{"repo": "cavos-io/rtp-agent", "files": {"app/app.go": {"a87dc924b5783f20b3aa899d02a5cc675467a2d3": {"t": 1785745647.5829659, "n": 348081, "h": "5f8e901d336fd8c6", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.LLMOption {\n\topts := []adaptergoogle.LLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.RealtimeOption {\n\topts := []adaptergoogle.RealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.STTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.TTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n\tcloseOnce sync.Once\n\tcloseErr error\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeOnce.Do(func() {\n\t\ta.closeMCPServers()\n\t\tvar detectorErr error\n\t\tif a.Agent != nil {\n\t\t\tif closer, ok := a.Agent.AudioTurnDetector.(interface{ Close() error }); ok {\n\t\t\t\tdetectorErr = closer.Close()\n\t\t\t}\n\t\t}\n\t\tvar telemetryErr error\n\t\tif a.telemetryLogs {\n\t\t\ta.telemetryLogs = false\n\t\t\ttelemetryErr = appShutdownLoggerProvider(ctx)\n\t\t}\n\t\ta.closeErr = errors.Join(detectorErr, telemetryErr)\n\t})\n\treturn a.closeErr\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNamePartsSet: true,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t\troomIO.ReconcileParticipants()\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t\troomIO.ReconcileParticipants()\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), workerlivekit.RecordingFileName), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewAvatar(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.AvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.LLM, error) {\n\treturn adapteraws.NewLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.LLMOption {\n\tllmOpts := []adapteraws.LLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.LLMOption {\n\tllmOpts := []anthropic.LLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewLLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.LLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProviderWithVAD(cfg, provider, a.VAD)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\treturn fallbackSTTFromProviderWithVAD(cfg, provider, nil)\n}\n\nfunc fallbackSTTFromProviderWithVAD(cfg AppConfig, provider string, detector corevad.VAD) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.STTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewSTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewSTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, detector) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(detector))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.STTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewSTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.STT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.STTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.LLMOption) (llm.LLM, error) {\n\treturn azure.NewLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.LLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.TTSOption {\n\tttsOpts := []azure.TTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.STT, error) {\n\tsttOpts := []adapteraws.STTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.STT {\n\tsttOpts := []fal.STTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.STT, error) {\n\tsttOpts := []groq.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.LLM {\n\tllmOpts := []groq.LLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.LLM {\n\tllmOpts := []telnyx.LLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.LLMOption {\n\topts := []openai.LLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.LLMOption {\n\topts := []openai.LLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.TTSOption {\n\tttsOpts := []elevenlabs.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.TTSOption {\n\tttsOpts := []rime.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewTTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewTTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewTTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewTTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewTTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.LLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewLLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewSTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.STTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, a.VAD) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(a.VAD))\n\t\t}\n\t\ta.STT = speechmatics.NewSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewSTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.STTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewSTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewTTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewTTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewTTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewTTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewTTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.RealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerSpeechmatics:\n\t\treturn speechmatics.NewRealtimeModel(cfg.SpeechmaticsAPIKey, speechmaticsRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc speechmaticsRealtimeOptionsFromConfig(cfg AppConfig) []speechmatics.RealtimeOption {\n\topts := []speechmatics.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\treturn opts\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.RealtimeOption {\n\topts := []adapteraws.RealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.STTOption {\n\tsttOpts := []nvidia.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{\n\t\t\t\tLabel: label,\n\t\t\t\tSpeakerIdentifiers: []string{speakerID},\n\t\t\t})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsShouldUseAppVAD(cfg AppConfig, detector corevad.VAD) bool {\n\tif detector == nil {\n\t\treturn false\n\t}\n\treturn speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) || normalizeProvider(cfg.VADProvider) != \"\"\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.STTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.STTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "1a1ed75ca28b3563f67fcc86c0330f646ff8e4cc": {"t": 1785755344.9669354, "n": 348081, "h": "5f8e901d336fd8c6", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.LLMOption {\n\topts := []adaptergoogle.LLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.RealtimeOption {\n\topts := []adaptergoogle.RealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.STTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.TTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n\tcloseOnce sync.Once\n\tcloseErr error\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeOnce.Do(func() {\n\t\ta.closeMCPServers()\n\t\tvar detectorErr error\n\t\tif a.Agent != nil {\n\t\t\tif closer, ok := a.Agent.AudioTurnDetector.(interface{ Close() error }); ok {\n\t\t\t\tdetectorErr = closer.Close()\n\t\t\t}\n\t\t}\n\t\tvar telemetryErr error\n\t\tif a.telemetryLogs {\n\t\t\ta.telemetryLogs = false\n\t\t\ttelemetryErr = appShutdownLoggerProvider(ctx)\n\t\t}\n\t\ta.closeErr = errors.Join(detectorErr, telemetryErr)\n\t})\n\treturn a.closeErr\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNamePartsSet: true,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t\troomIO.ReconcileParticipants()\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t\troomIO.ReconcileParticipants()\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), workerlivekit.RecordingFileName), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewAvatar(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.AvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.LLM, error) {\n\treturn adapteraws.NewLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.LLMOption {\n\tllmOpts := []adapteraws.LLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.LLMOption {\n\tllmOpts := []anthropic.LLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewLLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.LLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProviderWithVAD(cfg, provider, a.VAD)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\treturn fallbackSTTFromProviderWithVAD(cfg, provider, nil)\n}\n\nfunc fallbackSTTFromProviderWithVAD(cfg AppConfig, provider string, detector corevad.VAD) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.STTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewSTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewSTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, detector) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(detector))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.STTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewSTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.STT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.STTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.LLMOption) (llm.LLM, error) {\n\treturn azure.NewLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.LLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.TTSOption {\n\tttsOpts := []azure.TTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.STT, error) {\n\tsttOpts := []adapteraws.STTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.STT {\n\tsttOpts := []fal.STTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.STT, error) {\n\tsttOpts := []groq.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.LLM {\n\tllmOpts := []groq.LLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.LLM {\n\tllmOpts := []telnyx.LLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.LLMOption {\n\topts := []openai.LLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.LLMOption {\n\topts := []openai.LLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.TTSOption {\n\tttsOpts := []elevenlabs.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.TTSOption {\n\tttsOpts := []rime.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewTTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewTTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewTTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewTTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewTTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.LLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewLLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewSTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.STTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, a.VAD) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(a.VAD))\n\t\t}\n\t\ta.STT = speechmatics.NewSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewSTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.STTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewSTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewTTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewTTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewTTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewTTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewTTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.RealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerSpeechmatics:\n\t\treturn speechmatics.NewRealtimeModel(cfg.SpeechmaticsAPIKey, speechmaticsRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc speechmaticsRealtimeOptionsFromConfig(cfg AppConfig) []speechmatics.RealtimeOption {\n\topts := []speechmatics.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\treturn opts\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.RealtimeOption {\n\topts := []adapteraws.RealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.STTOption {\n\tsttOpts := []nvidia.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{\n\t\t\t\tLabel: label,\n\t\t\t\tSpeakerIdentifiers: []string{speakerID},\n\t\t\t})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsShouldUseAppVAD(cfg AppConfig, detector corevad.VAD) bool {\n\tif detector == nil {\n\t\treturn false\n\t}\n\treturn speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) || normalizeProvider(cfg.VADProvider) != \"\"\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.STTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.STTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "93ab58f2320eef3d10184b21224579d9bf10e08e": {"t": 1785755347.2419994, "n": 348012, "h": "f23ea98be453bb83", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.LLMOption {\n\topts := []adaptergoogle.LLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.RealtimeOption {\n\topts := []adaptergoogle.RealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.STTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.TTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n\tcloseOnce sync.Once\n\tcloseErr error\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeOnce.Do(func() {\n\t\ta.closeMCPServers()\n\t\tvar detectorErr error\n\t\tif a.Agent != nil {\n\t\t\tif closer, ok := a.Agent.AudioTurnDetector.(interface{ Close() error }); ok {\n\t\t\t\tdetectorErr = closer.Close()\n\t\t\t}\n\t\t}\n\t\tvar telemetryErr error\n\t\tif a.telemetryLogs {\n\t\t\ta.telemetryLogs = false\n\t\t\ttelemetryErr = appShutdownLoggerProvider(ctx)\n\t\t}\n\t\ta.closeErr = errors.Join(detectorErr, telemetryErr)\n\t})\n\treturn a.closeErr\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNamePartsSet: true,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), workerlivekit.RecordingFileName), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewAvatar(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.AvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.LLM, error) {\n\treturn adapteraws.NewLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.LLMOption {\n\tllmOpts := []adapteraws.LLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.LLMOption {\n\tllmOpts := []anthropic.LLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewLLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.LLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProviderWithVAD(cfg, provider, a.VAD)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\treturn fallbackSTTFromProviderWithVAD(cfg, provider, nil)\n}\n\nfunc fallbackSTTFromProviderWithVAD(cfg AppConfig, provider string, detector corevad.VAD) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.STTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewSTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewSTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, detector) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(detector))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.STTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewSTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.STT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.STTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.LLMOption) (llm.LLM, error) {\n\treturn azure.NewLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.LLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.TTSOption {\n\tttsOpts := []azure.TTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.STT, error) {\n\tsttOpts := []adapteraws.STTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.STT {\n\tsttOpts := []fal.STTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.STT, error) {\n\tsttOpts := []groq.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.LLM {\n\tllmOpts := []groq.LLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.LLM {\n\tllmOpts := []telnyx.LLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.LLMOption {\n\topts := []openai.LLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.LLMOption {\n\topts := []openai.LLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.TTSOption {\n\tttsOpts := []elevenlabs.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.TTSOption {\n\tttsOpts := []rime.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewTTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewTTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewTTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewTTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewTTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.LLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewLLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewSTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.STTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, a.VAD) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(a.VAD))\n\t\t}\n\t\ta.STT = speechmatics.NewSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewSTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.STTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewSTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewTTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewTTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.TTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewTTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.TTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.TTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewTTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewTTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.RealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerSpeechmatics:\n\t\treturn speechmatics.NewRealtimeModel(cfg.SpeechmaticsAPIKey, speechmaticsRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc speechmaticsRealtimeOptionsFromConfig(cfg AppConfig) []speechmatics.RealtimeOption {\n\topts := []speechmatics.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\treturn opts\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.RealtimeOption {\n\topts := []adapteraws.RealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.STTOption {\n\tsttOpts := []nvidia.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{\n\t\t\t\tLabel: label,\n\t\t\t\tSpeakerIdentifiers: []string{speakerID},\n\t\t\t})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsShouldUseAppVAD(cfg AppConfig, detector corevad.VAD) bool {\n\tif detector == nil {\n\t\treturn false\n\t}\n\treturn speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) || normalizeProvider(cfg.VADProvider) != \"\"\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.STTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.STTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "e641dcda8f83f7b1715b42266879384e6475e21b": {"t": 1785755350.5382535, "n": 350038, "h": "4401bafb5ec5914b", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n\tcloseOnce sync.Once\n\tcloseErr error\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeOnce.Do(func() {\n\t\ta.closeMCPServers()\n\t\tvar detectorErr error\n\t\tif a.Agent != nil {\n\t\t\tif closer, ok := a.Agent.AudioTurnDetector.(interface{ Close() error }); ok {\n\t\t\t\tdetectorErr = closer.Close()\n\t\t\t}\n\t\t}\n\t\tvar telemetryErr error\n\t\tif a.telemetryLogs {\n\t\t\ta.telemetryLogs = false\n\t\t\ttelemetryErr = appShutdownLoggerProvider(ctx)\n\t\t}\n\t\ta.closeErr = errors.Join(detectorErr, telemetryErr)\n\t})\n\treturn a.closeErr\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNamePartsSet: true,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), workerlivekit.RecordingFileName), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProviderWithVAD(cfg, provider, a.VAD)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\treturn fallbackSTTFromProviderWithVAD(cfg, provider, nil)\n}\n\nfunc fallbackSTTFromProviderWithVAD(cfg AppConfig, provider string, detector corevad.VAD) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, detector) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(detector))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, a.VAD) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(a.VAD))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerSpeechmatics:\n\t\treturn speechmatics.NewRealtimeModel(cfg.SpeechmaticsAPIKey, speechmaticsRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc speechmaticsRealtimeOptionsFromConfig(cfg AppConfig) []speechmatics.RealtimeOption {\n\topts := []speechmatics.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\treturn opts\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{\n\t\t\t\tLabel: label,\n\t\t\t\tSpeakerIdentifiers: []string{speakerID},\n\t\t\t})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsShouldUseAppVAD(cfg AppConfig, detector corevad.VAD) bool {\n\tif detector == nil {\n\t\treturn false\n\t}\n\treturn speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) || normalizeProvider(cfg.VADProvider) != \"\"\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.SpeechmaticsSTTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.SpeechmaticsSTTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "a33af864cfa47d3331ceb7728671d94b99f86c83": {"t": 1785755352.4941144, "n": 350018, "h": "8655f56ab7d9425b", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n\tcloseOnce sync.Once\n\tcloseErr error\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeOnce.Do(func() {\n\t\ta.closeMCPServers()\n\t\tvar detectorErr error\n\t\tif a.Agent != nil {\n\t\t\tif closer, ok := a.Agent.AudioTurnDetector.(interface{ Close() error }); ok {\n\t\t\t\tdetectorErr = closer.Close()\n\t\t\t}\n\t\t}\n\t\tvar telemetryErr error\n\t\tif a.telemetryLogs {\n\t\t\ta.telemetryLogs = false\n\t\t\ttelemetryErr = appShutdownLoggerProvider(ctx)\n\t\t}\n\t\ta.closeErr = errors.Join(detectorErr, telemetryErr)\n\t})\n\treturn a.closeErr\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNamePartsSet: true,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProviderWithVAD(cfg, provider, a.VAD)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\treturn fallbackSTTFromProviderWithVAD(cfg, provider, nil)\n}\n\nfunc fallbackSTTFromProviderWithVAD(cfg AppConfig, provider string, detector corevad.VAD) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, detector) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(detector))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, a.VAD) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(a.VAD))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerSpeechmatics:\n\t\treturn speechmatics.NewRealtimeModel(cfg.SpeechmaticsAPIKey, speechmaticsRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc speechmaticsRealtimeOptionsFromConfig(cfg AppConfig) []speechmatics.RealtimeOption {\n\topts := []speechmatics.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\treturn opts\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{\n\t\t\t\tLabel: label,\n\t\t\t\tSpeakerIdentifiers: []string{speakerID},\n\t\t\t})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsShouldUseAppVAD(cfg AppConfig, detector corevad.VAD) bool {\n\tif detector == nil {\n\t\treturn false\n\t}\n\treturn speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) || normalizeProvider(cfg.VADProvider) != \"\"\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.SpeechmaticsSTTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.SpeechmaticsSTTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "fb93ca4ca02c6c45edce9ed6f3f15d85da441baf": {"t": 1785755354.523381, "n": 349671, "h": "c7b0b0710f4f75f4", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNamePartsSet: true,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProviderWithVAD(cfg, provider, a.VAD)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\treturn fallbackSTTFromProviderWithVAD(cfg, provider, nil)\n}\n\nfunc fallbackSTTFromProviderWithVAD(cfg AppConfig, provider string, detector corevad.VAD) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, detector) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(detector))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, a.VAD) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(a.VAD))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerSpeechmatics:\n\t\treturn speechmatics.NewRealtimeModel(cfg.SpeechmaticsAPIKey, speechmaticsRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc speechmaticsRealtimeOptionsFromConfig(cfg AppConfig) []speechmatics.RealtimeOption {\n\topts := []speechmatics.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\treturn opts\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{\n\t\t\t\tLabel: label,\n\t\t\t\tSpeakerIdentifiers: []string{speakerID},\n\t\t\t})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsShouldUseAppVAD(cfg AppConfig, detector corevad.VAD) bool {\n\tif detector == nil {\n\t\treturn false\n\t}\n\treturn speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) || normalizeProvider(cfg.VADProvider) != \"\"\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.SpeechmaticsSTTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.SpeechmaticsSTTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "487463fae0d48f5dc60e238841fb6ec9c6f55500": {"t": 1785755358.5932806, "n": 349639, "h": "9844604906e667d4", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProviderWithVAD(cfg, provider, a.VAD)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\treturn fallbackSTTFromProviderWithVAD(cfg, provider, nil)\n}\n\nfunc fallbackSTTFromProviderWithVAD(cfg AppConfig, provider string, detector corevad.VAD) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, detector) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(detector))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, a.VAD) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(a.VAD))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerSpeechmatics:\n\t\treturn speechmatics.NewRealtimeModel(cfg.SpeechmaticsAPIKey, speechmaticsRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc speechmaticsRealtimeOptionsFromConfig(cfg AppConfig) []speechmatics.RealtimeOption {\n\topts := []speechmatics.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, speechmatics.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, speechmatics.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\treturn opts\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{\n\t\t\t\tLabel: label,\n\t\t\t\tSpeakerIdentifiers: []string{speakerID},\n\t\t\t})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsShouldUseAppVAD(cfg AppConfig, detector corevad.VAD) bool {\n\tif detector == nil {\n\t\treturn false\n\t}\n\treturn speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) || normalizeProvider(cfg.VADProvider) != \"\"\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.SpeechmaticsSTTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.SpeechmaticsSTTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "b7b29d4ca338b960838f10de5fcc865103f865d0": {"t": 1785755360.5188487, "n": 348493, "h": "e079a4cebcb76e76", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProviderWithVAD(cfg, provider, a.VAD)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\treturn fallbackSTTFromProviderWithVAD(cfg, provider, nil)\n}\n\nfunc fallbackSTTFromProviderWithVAD(cfg AppConfig, provider string, detector corevad.VAD) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, detector) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(detector))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, a.VAD) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(a.VAD))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{\n\t\t\t\tLabel: label,\n\t\t\t\tSpeakerIdentifiers: []string{speakerID},\n\t\t\t})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsShouldUseAppVAD(cfg AppConfig, detector corevad.VAD) bool {\n\tif detector == nil {\n\t\treturn false\n\t}\n\treturn speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) || normalizeProvider(cfg.VADProvider) != \"\"\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.SpeechmaticsSTTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.SpeechmaticsSTTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "93c89493d52c3ff9b6c39ff9b0105eda451b2045": {"t": 1785755363.713051, "n": 348447, "h": "be56f596daa77ebe", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProviderWithVAD(cfg, provider, a.VAD)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\treturn fallbackSTTFromProviderWithVAD(cfg, provider, nil)\n}\n\nfunc fallbackSTTFromProviderWithVAD(cfg AppConfig, provider string, detector corevad.VAD) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, detector) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(detector))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\tif speechmaticsShouldUseAppVAD(cfg, a.VAD) {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTVAD(a.VAD))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsShouldUseAppVAD(cfg AppConfig, detector corevad.VAD) bool {\n\tif detector == nil {\n\t\treturn false\n\t}\n\treturn speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) || normalizeProvider(cfg.VADProvider) != \"\"\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.SpeechmaticsSTTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.SpeechmaticsSTTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "ada2c974325c867d7fab705aa30661c60fdf2669": {"t": 1785755366.4321892, "n": 347768, "h": "0519880c50f05a33", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\tif appConfigUsesSpeechmaticsExternalSTT(cfg) {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t}\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc appConfigUsesSpeechmaticsExternalSTT(cfg AppConfig) bool {\n\tif !speechmaticsExternalTurnDetectionMode(cfg.STTTurnDetectionMode) {\n\t\treturn false\n\t}\n\tif normalizeProvider(cfg.STTProvider) == providerSpeechmatics {\n\t\treturn true\n\t}\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tif normalizeProvider(provider) == providerSpeechmatics {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOptionFromConfig(cfg); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsExternalTurnDetectionMode(mode string) bool {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\", \"external\":\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc speechmaticsTurnDetectionOptionFromConfig(cfg AppConfig) (speechmatics.SpeechmaticsSTTOption, error) {\n\tif normalizeProvider(cfg.VADProvider) != \"\" {\n\t\treturn nil, nil\n\t}\n\treturn speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode)\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.SpeechmaticsSTTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "2e004f3f919a2bfcaf9b4a142eac6e0c3e4813fc": {"t": 1785755369.8919325, "n": 346892, "h": "e4b1292ad304eb57", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTurnDetectionMode string\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTurnDetectionMode: normalizedEnv(\"RTP_AGENT_STT_TURN_DETECTION_MODE\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif opt, err := speechmaticsTurnDetectionOption(cfg.STTTurnDetectionMode); err != nil {\n\t\t\treturn nil, err\n\t\t} else if opt != nil {\n\t\t\tsttOpts = append(sttOpts, opt)\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsTurnDetectionOption(mode string) (speechmatics.SpeechmaticsSTTOption, error) {\n\tswitch strings.ToLower(strings.TrimSpace(mode)) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"external\":\n\t\treturn nil, nil\n\tcase \"fixed\":\n\t\treturn speechmatics.WithSpeechmaticsSTTFixedTurnDetection(), nil\n\tcase \"adaptive\":\n\t\treturn speechmatics.WithSpeechmaticsSTTAdaptiveTurnDetection(), nil\n\tcase \"smart\", \"smart-turn\", \"smart_turn\":\n\t\treturn speechmatics.WithSpeechmaticsSTTSmartTurnDetection(), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported Speechmatics STT turn detection mode %q\", mode)\n\t}\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "8b110f30f807707616e7472ba1042a46c0a6d9fe": {"t": 1785755371.8172848, "n": 345814, "h": "0276d433bc68a66e", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t// App uses the lower-level RoomIO path because it configures recorder and\n\t\t// room-tool internals. Normal worker entrypoints should prefer\n\t\t// JobContext.StartSession so RoomIO does not become the room event API.\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "73c2c54ed73eb46c70238451c0eac3dad98b1ccd": {"t": 1785755374.1572306, "n": 345596, "h": "1cae3b7e017e2274", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := ctx.AddShutdownCallback(func() {\n\t\t\t\t_ = a.Session.Stop(context.Background())\n\t\t\t\t_ = roomIO.Close()\n\t\t\t}); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to register RoomIO teardown on job shutdown\", err)\n\t\t\t}\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "2bccb747fc2b299ab9949a4493acdf4e3002f630": {"t": 1785755377.2868967, "n": 345373, "h": "24acc62e7cc20e98", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\tttsOpts := []upliftai.UpliftAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, upliftai.WithUpliftAIBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "ca0fad9fe24730277ddf82c3ca46d0e23094eb4e": {"t": 1785755383.3493814, "n": 345047, "h": "d1e6fbca868f8193", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ultravox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginMetadata(ultravox.PluginTitle, ultravox.PluginVersion, ultravox.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeBaseURL string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeBaseURL: os.Getenv(\"RTP_AGENT_REALTIME_BASE_URL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerUltravox:\n\t\treturn ultravox.NewRealtimeModel(cfg.UltravoxAPIKey, ultravoxRealtimeOptionsFromConfig(cfg)...)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc ultravoxRealtimeOptionsFromConfig(cfg AppConfig) []ultravox.RealtimeOption {\n\topts := []ultravox.RealtimeOption{}\n\tif cfg.RealtimeModel != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeModel(cfg.RealtimeModel))\n\t}\n\tif cfg.RealtimeVoice != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeVoice(cfg.RealtimeVoice))\n\t}\n\tif cfg.RealtimeBaseURL != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeBaseURL(cfg.RealtimeBaseURL))\n\t}\n\tif systemPrompt := modelOptionString(cfg.RealtimeModelOptions, \"system_prompt\"); systemPrompt != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeSystemPrompt(systemPrompt))\n\t}\n\tif outputMedium := modelOptionString(cfg.RealtimeModelOptions, \"output_medium\"); outputMedium != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeOutputMedium(outputMedium))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"input_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeInputSampleRate(sampleRate))\n\t}\n\tif sampleRate := modelOptionInt(cfg.RealtimeModelOptions, \"output_sample_rate\"); sampleRate > 0 {\n\t\topts = append(opts, ultravox.WithRealtimeOutputSampleRate(sampleRate))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, ultravox.WithRealtimeTemperature(*temperature))\n\t}\n\tif languageHint := modelOptionString(cfg.RealtimeModelOptions, \"language_hint\"); languageHint != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeLanguageHint(languageHint))\n\t}\n\tif maxDuration := modelOptionString(cfg.RealtimeModelOptions, \"max_duration\"); maxDuration != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeMaxDuration(maxDuration))\n\t}\n\tif message := modelOptionString(cfg.RealtimeModelOptions, \"time_exceeded_message\"); message != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeTimeExceededMessage(message))\n\t}\n\tif enableGreeting := modelOptionBool(cfg.RealtimeModelOptions, \"enable_greeting_prompt\"); enableGreeting != nil {\n\t\topts = append(opts, ultravox.WithRealtimeEnableGreetingPrompt(*enableGreeting))\n\t}\n\tif firstSpeaker := modelOptionString(cfg.RealtimeModelOptions, \"first_speaker\"); firstSpeaker != \"\" {\n\t\topts = append(opts, ultravox.WithRealtimeFirstSpeaker(firstSpeaker))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "2fb8d1aa5c3cae835ef3ea77a90f95ef0571f5bb": {"t": 1785755385.8789685, "n": 342417, "h": "b117a73f64af0062", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyxLLMFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc telnyxLLMFromConfig(cfg AppConfig) *telnyx.TelnyxLLM {\n\tllmOpts := []telnyx.TelnyxLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif openAIOpts := telnyxOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, telnyx.WithTelnyxLLMOptions(openAIOpts...))\n\t}\n\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc telnyxOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyxLLMFromConfig(cfg)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "25dae2dc9e82c0b5baadadc76c06f28d8b1f9e81": {"t": 1785755388.3937051, "n": 340547, "h": "cebb5622aa3e3d8f", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc anthropicLLMOptionsFromConfig(cfg AppConfig) []anthropic.AnthropicOption {\n\tllmOpts := []anthropic.AnthropicOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicUser(user))\n\t}\n\tif maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t} else if maxTokens := modelOptionInt(cfg.LLMModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicMaxTokens(maxTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTemperature(*temperature))\n\t}\n\tif topK := modelOptionInt(cfg.LLMModelOptions, \"top_k\"); topK > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicTopK(topK))\n\t}\n\tif caching := modelOptionString(cfg.LLMModelOptions, \"caching\"); caching != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicCaching(caching))\n\t}\n\tif strictToolSchema := modelOptionBool(cfg.LLMModelOptions, \"strict_tool_schema\"); strictToolSchema != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicStrictToolSchema(*strictToolSchema))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicParallelToolCalls(*parallelToolCalls))\n\t}\n\tif readTimeoutMS, ok := modelOptionIntValue(cfg.LLMModelOptions, \"read_timeout_ms\"); ok && readTimeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicReadTimeout(time.Duration(readTimeoutMS)*time.Millisecond))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, anthropicLLMOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tfor _, key := range anthropicLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar anthropicLLMExtraParamKeys = []string{\n\t\"metadata\",\n\t\"service_tier\",\n\t\"stop_sequences\",\n\t\"thinking\",\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "dff51d12fc8f0fe711a2aa6bf400300ad45a90d6": {"t": 1785755390.6920805, "n": 338355, "h": "b73c80499ed38451", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tvolumeGainDBSet bool\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDBSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t\tgoogleCfg.volumeGainDBSet = true\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc rimeTTSOptionsFromConfig(cfg AppConfig) []rime.RimeTTSOption {\n\tttsOpts := []rime.RimeTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSWebsocketURL != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif cfg.TTSSpeed != 0 {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t}\n\tif cfg.TTSDeliveryMode != \"\" {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t}\n\tif cfg.TTSTemperature != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTemperature(*cfg.TTSTemperature))\n\t}\n\tif cfg.TTSTopP != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTopP(*cfg.TTSTopP))\n\t}\n\tif cfg.TTSMaxTokens != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSMaxTokens(*cfg.TTSMaxTokens))\n\t}\n\tif repetitionPenalty := modelOptionFloat(cfg.TTSModelOptions, \"repetition_penalty\"); repetitionPenalty != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSRepetitionPenalty(*repetitionPenalty))\n\t}\n\tif speedAlpha := modelOptionFloat(cfg.TTSModelOptions, \"speed_alpha\"); speedAlpha != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSpeedAlpha(*speedAlpha))\n\t}\n\tif reduceLatency := modelOptionBool(cfg.TTSModelOptions, \"reduce_latency\"); reduceLatency != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSReduceLatency(*reduceLatency))\n\t}\n\tif pauseBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"pause_between_brackets\"); pauseBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPauseBetweenBrackets(*pauseBetweenBrackets))\n\t}\n\tif phonemizeBetweenBrackets := modelOptionBool(cfg.TTSModelOptions, \"phonemize_between_brackets\"); phonemizeBetweenBrackets != nil {\n\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSPhonemizeBetweenBrackets(*phonemizeBetweenBrackets))\n\t}\n\treturn ttsOpts\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, rimeTTSOptionsFromConfig(cfg)...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "919fd4e9f1ec89f3ff2778fb7c02b6975d187238": {"t": 1785755412.9353178, "n": 337695, "h": "21fdc7ea8493ee83", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlanguageSet bool\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tvoiceSet bool\n\tgender string\n\tgenderSet bool\n\tcloneKey string\n\tcloneKeySet bool\n\tmodel string\n\tmodelSet bool\n\tprompt string\n\tpromptSet bool\n\tspeakingRate float64\n\tspeakingRateSet bool\n\tpitch float64\n\tpitchSet bool\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tlocationSet bool\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.locationSet {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlocationSet bool\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.locationSet {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.languageSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.locationSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voiceSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.genderSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKeySet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.modelSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.promptSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRateSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitchSet {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlocationSet: cfg.STTRegion != \"\",\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.STTModelOptions, \"model\")\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.STTModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif googleCfg.language == \"\" && len(googleCfg.alternativeLanguages) == 0 {\n\t\tif languages := modelOptionStringList(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\t\tgoogleCfg.language = languages[0]\n\t\t\tgoogleCfg.alternativeLanguages = append([]string(nil), languages[1:]...)\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.STTModelOptions, \"use_streaming\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.STTModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.punctuate == nil {\n\t\tgoogleCfg.punctuate = modelOptionBool(cfg.STTModelOptions, \"punctuate\")\n\t}\n\tif googleCfg.spokenPunctuation == nil {\n\t\tgoogleCfg.spokenPunctuation = modelOptionBool(cfg.STTModelOptions, \"spoken_punctuation\")\n\t}\n\tif googleCfg.profanityFilter == nil {\n\t\tgoogleCfg.profanityFilter = modelOptionBool(cfg.STTModelOptions, \"profanity_filter\")\n\t}\n\tif googleCfg.detectLanguage == nil {\n\t\tgoogleCfg.detectLanguage = modelOptionBool(cfg.STTModelOptions, \"detect_language\")\n\t}\n\tif googleCfg.interimResults == nil {\n\t\tgoogleCfg.interimResults = modelOptionBool(cfg.STTModelOptions, \"interim_results\")\n\t}\n\tif googleCfg.voiceActivityEvents == nil {\n\t\tgoogleCfg.voiceActivityEvents = modelOptionBool(cfg.STTModelOptions, \"enable_voice_activity_events\")\n\t}\n\tif googleCfg.wordTimeOffsets == nil {\n\t\tgoogleCfg.wordTimeOffsets = modelOptionBool(cfg.STTModelOptions, \"enable_word_time_offsets\")\n\t}\n\tif googleCfg.wordConfidence == nil {\n\t\tgoogleCfg.wordConfidence = modelOptionBool(cfg.STTModelOptions, \"enable_word_confidence\")\n\t}\n\tif googleCfg.minConfidence == nil {\n\t\tgoogleCfg.minConfidence = modelOptionFloat(cfg.STTModelOptions, \"min_confidence_threshold\")\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t} else if speechEndTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_end_timeout\"); speechEndTimeout != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*speechEndTimeout * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t} else if speechStartTimeout := modelOptionFloat(cfg.STTModelOptions, \"speech_start_timeout\"); speechStartTimeout != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*speechStartTimeout * float64(time.Second))\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlanguageSet: cfg.TTSLanguage != \"\",\n\t\tlocation: cfg.TTSRegion,\n\t\tlocationSet: cfg.TTSRegion != \"\",\n\t\tvoice: cfg.TTSVoice,\n\t\tvoiceSet: cfg.TTSVoice != \"\",\n\t\tgender: cfg.TTSGender,\n\t\tgenderSet: cfg.TTSGender != \"\",\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tcloneKeySet: cfg.TTSVoiceID != \"\",\n\t\tmodel: cfg.TTSModel,\n\t\tmodelSet: cfg.TTSModel != \"\",\n\t\tprompt: cfg.TTSInstructions,\n\t\tpromptSet: cfg.TTSInstructions != \"\",\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif !googleCfg.voiceSet {\n\t\tif voice, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_name\"); ok {\n\t\t\tgoogleCfg.voice = voice\n\t\t\tgoogleCfg.voiceSet = true\n\t\t}\n\t}\n\tif !googleCfg.languageSet {\n\t\tif language, ok := modelOptionStringValue(cfg.TTSModelOptions, \"language\"); ok {\n\t\t\tgoogleCfg.language = language\n\t\t\tgoogleCfg.languageSet = true\n\t\t}\n\t}\n\tif !googleCfg.locationSet {\n\t\tif location, ok := modelOptionStringValue(cfg.TTSModelOptions, \"location\"); ok {\n\t\t\tgoogleCfg.location = location\n\t\t\tgoogleCfg.locationSet = true\n\t\t}\n\t}\n\tif !googleCfg.genderSet {\n\t\tif gender, ok := modelOptionStringValue(cfg.TTSModelOptions, \"gender\"); ok {\n\t\t\tgoogleCfg.gender = gender\n\t\t\tgoogleCfg.genderSet = true\n\t\t}\n\t}\n\tif !googleCfg.cloneKeySet {\n\t\tif cloneKey, ok := modelOptionStringValue(cfg.TTSModelOptions, \"voice_cloning_key\"); ok {\n\t\t\tgoogleCfg.cloneKey = cloneKey\n\t\t\tgoogleCfg.cloneKeySet = true\n\t\t}\n\t}\n\tif !googleCfg.modelSet {\n\t\tif modelName, ok := modelOptionStringValue(cfg.TTSModelOptions, \"model_name\"); ok {\n\t\t\tgoogleCfg.model = modelName\n\t\t\tgoogleCfg.modelSet = true\n\t\t}\n\t}\n\tif !googleCfg.promptSet {\n\t\tif prompt, ok := modelOptionStringValue(cfg.TTSModelOptions, \"prompt\"); ok {\n\t\t\tgoogleCfg.prompt = prompt\n\t\t\tgoogleCfg.promptSet = true\n\t\t}\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.ssml == nil {\n\t\tgoogleCfg.ssml = modelOptionBool(cfg.TTSModelOptions, \"enable_ssml\")\n\t}\n\tif googleCfg.markup == nil && !boolValue(googleCfg.ssml) {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t\tgoogleCfg.speakingRateSet = true\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t\tgoogleCfg.speakingRateSet = true\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t\tgoogleCfg.pitchSet = true\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t\tgoogleCfg.pitchSet = true\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.LLMModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tif location, ok := modelOptionStringValue(cfg.RealtimeModelOptions, \"location\"); ok {\n\t\tgoogleCfg.location = location\n\t\tgoogleCfg.locationSet = true\n\t}\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\ttext, _ := modelOptionStringValue(options, key)\n\treturn text\n}\n\nfunc modelOptionStringValue(options map[string]any, key string) (string, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\treturn strings.TrimSpace(text), true\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "5a95421d684c17711903c37f9d607a9a3665cec6": {"t": 1785755415.1435971, "n": 333220, "h": "bf9fdec1f3fc556f", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/cavos-io/rtp-agent/library/utils/images\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlocation string\n\tvoice string\n\tgender string\n\tcloneKey string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.location != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tvoice string\n\tinstructions string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tcandidateCount int\n\tpresencePenalty *float64\n\tfrequencyPenalty *float64\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\thttpOptions *genai.HTTPOptions\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\timageEncodeOptions *images.EncodeOptions\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.location != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.instructions != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInstructions(c.instructions))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.candidateCount > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeCandidateCount(c.candidateCount))\n\t}\n\tif c.presencePenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimePresencePenalty(*c.presencePenalty))\n\t}\n\tif c.frequencyPenalty != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeFrequencyPenalty(*c.frequencyPenalty))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.httpOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeHTTPOptions(c.httpOptions))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.imageEncodeOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeImageEncodeOptions(*c.imageEncodeOptions))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.location != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.location != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.gender != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKey != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationFromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationFromModelOption(value any) *speechpb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechpb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTPhraseSetPhrasesFromModelOption(phraseSetOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechpb.PhraseSet{\n\t\t\tName: modelOptionString(phraseSetOptions, \"name\"),\n\t\t\tPhrases: phrases,\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTPhraseSetPhrasesFromModelOption(value any) []*speechpb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechpb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechpb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\tif adaptation := googleSTTAdaptationV2FromModelOption(options[\"speech_adaptation\"]); adaptation != nil {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromModelOption(value any) *speechv2pb.SpeechAdaptation {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphraseSetValues, ok := anySlice(options[\"phrase_sets\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tadaptation := &speechv2pb.SpeechAdaptation{}\n\tfor _, phraseSetValue := range phraseSetValues {\n\t\tphraseSetOptions, ok := phraseSetValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tinlineOptions, ok := phraseSetOptions[\"inline_phrase_set\"].(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrases := googleSTTV2PhraseSetPhrasesFromModelOption(inlineOptions[\"phrases\"])\n\t\tif len(phrases) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tadaptation.PhraseSets = append(adaptation.PhraseSets, &speechv2pb.SpeechAdaptation_AdaptationPhraseSet{\n\t\t\tValue: &speechv2pb.SpeechAdaptation_AdaptationPhraseSet_InlinePhraseSet{\n\t\t\t\tInlinePhraseSet: &speechv2pb.PhraseSet{Phrases: phrases},\n\t\t\t},\n\t\t})\n\t}\n\tif len(adaptation.PhraseSets) == 0 {\n\t\treturn nil\n\t}\n\treturn adaptation\n}\n\nfunc googleSTTV2PhraseSetPhrasesFromModelOption(value any) []*speechv2pb.PhraseSet_Phrase {\n\tphraseValues, ok := anySlice(value)\n\tif !ok {\n\t\treturn nil\n\t}\n\tphrases := make([]*speechv2pb.PhraseSet_Phrase, 0, len(phraseValues))\n\tfor _, phraseValue := range phraseValues {\n\t\tphraseOptions, ok := phraseValue.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\ttext := modelOptionString(phraseOptions, \"value\")\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := &speechv2pb.PhraseSet_Phrase{Value: text}\n\t\tif boost := modelOptionFloat(phraseOptions, \"boost\"); boost != nil {\n\t\t\tphrase.Boost = float32(*boost)\n\t\t}\n\t\tphrases = append(phrases, phrase)\n\t}\n\treturn phrases\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlocation: cfg.TTSRegion,\n\t\tvoice: cfg.TTSVoice,\n\t\tgender: cfg.TTSGender,\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif googleCfg.voice == \"\" {\n\t\tgoogleCfg.voice = modelOptionString(cfg.TTSModelOptions, \"voice_name\")\n\t}\n\tif googleCfg.cloneKey == \"\" {\n\t\tgoogleCfg.cloneKey = modelOptionString(cfg.TTSModelOptions, \"voice_cloning_key\")\n\t}\n\tif googleCfg.model == \"\" {\n\t\tgoogleCfg.model = modelOptionString(cfg.TTSModelOptions, \"model_name\")\n\t}\n\tif googleCfg.prompt == \"\" {\n\t\tgoogleCfg.prompt = modelOptionString(cfg.TTSModelOptions, \"prompt\")\n\t}\n\tif googleCfg.sampleRate == nil {\n\t\tif sampleRate, ok := modelOptionIntValue(cfg.TTSModelOptions, \"sample_rate\"); ok {\n\t\t\tgoogleCfg.sampleRate = &sampleRate\n\t\t}\n\t}\n\tif googleCfg.streaming == nil {\n\t\tgoogleCfg.streaming = modelOptionBool(cfg.TTSModelOptions, \"use_streaming\")\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif googleCfg.markup == nil && googleCfg.ssml == nil {\n\t\tgoogleCfg.markup = modelOptionBool(cfg.TTSModelOptions, \"use_markup\")\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t} else if speakingRate := modelOptionFloat(cfg.TTSModelOptions, \"speaking_rate\"); speakingRate != nil {\n\t\tgoogleCfg.speakingRate = *speakingRate\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t} else if pitch := modelOptionFloat(cfg.TTSModelOptions, \"pitch\"); pitch != nil {\n\t\tgoogleCfg.pitch = *pitch\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tvalue, ok := options[\"custom_pronunciations\"]\n\tif !ok {\n\t\treturn nil\n\t}\n\tif custom, ok := value.(*texttospeechpb.CustomPronunciations); ok {\n\t\treturn custom\n\t}\n\tconfig, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\titems, ok := anySlice(config[\"pronunciations\"])\n\tif !ok {\n\t\treturn nil\n\t}\n\tcustom := &texttospeechpb.CustomPronunciations{}\n\tfor _, item := range items {\n\t\titemMap, ok := item.(map[string]any)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tphrase := modelOptionString(itemMap, \"phrase\")\n\t\tpronunciation := modelOptionString(itemMap, \"pronunciation\")\n\t\tencoding, ok := googleTTSCustomPronunciationEncoding(itemMap)\n\t\tif phrase == \"\" || pronunciation == \"\" || !ok {\n\t\t\tcontinue\n\t\t}\n\t\tcustom.Pronunciations = append(custom.Pronunciations, &texttospeechpb.CustomPronunciationParams{\n\t\t\tPhrase: &phrase,\n\t\t\tPhoneticEncoding: &encoding,\n\t\t\tPronunciation: &pronunciation,\n\t\t})\n\t}\n\tif len(custom.Pronunciations) == 0 {\n\t\treturn nil\n\t}\n\treturn custom\n}\n\nfunc googleTTSCustomPronunciationEncoding(options map[string]any) (texttospeechpb.CustomPronunciationParams_PhoneticEncoding, bool) {\n\tvalue := modelOptionString(options, \"phonetic_encoding\")\n\tif value == \"\" {\n\t\tvalue = modelOptionString(options, \"phoneticEncoding\")\n\t}\n\tif value == \"\" {\n\t\treturn 0, false\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(value, \"-\", \"_\"))\n\tif enumValue, ok := texttospeechpb.CustomPronunciationParams_PhoneticEncoding_value[normalized]; ok {\n\t\treturn texttospeechpb.CustomPronunciationParams_PhoneticEncoding(enumValue), true\n\t}\n\treturn 0, false\n}\n\nfunc anySlice(value any) ([]any, bool) {\n\tswitch typed := value.(type) {\n\tcase []any:\n\t\treturn typed, true\n\tcase []map[string]any:\n\t\titems := make([]any, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\titems = append(items, item)\n\t\t}\n\t\treturn items, true\n\tdefault:\n\t\treturn nil, false\n\t}\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tif key == \"http_options\" {\n\t\t\t\tif httpOptions := googleHTTPOptionsFromModelOption(value); httpOptions != nil {\n\t\t\t\t\tparams[key] = httpOptions\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tgoogleCfg.location = modelOptionString(cfg.LLMModelOptions, \"location\")\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tgoogleCfg.location = modelOptionString(cfg.RealtimeModelOptions, \"location\")\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.instructions = modelOptionString(cfg.RealtimeModelOptions, \"instructions\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tgoogleCfg.candidateCount = modelOptionInt(cfg.RealtimeModelOptions, \"candidate_count\")\n\tgoogleCfg.presencePenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"presence_penalty\")\n\tgoogleCfg.frequencyPenalty = modelOptionFloat(cfg.RealtimeModelOptions, \"frequency_penalty\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.httpOptions = googleHTTPOptionsFromModelOption(cfg.RealtimeModelOptions[\"http_options\"])\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.imageEncodeOptions = googleRealtimeImageEncodeOptionsFromModelOption(cfg.RealtimeModelOptions[\"image_encode_options\"])\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeImageEncodeOptionsFromModelOption(value any) *images.EncodeOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := images.NewEncodeOptions()\n\tif format := modelOptionString(options, \"format\"); format != \"\" {\n\t\tresult.Format = format\n\t}\n\tif quality, ok := modelOptionIntValue(options, \"quality\"); ok {\n\t\tresult.Quality = quality\n\t}\n\tif width, ok := modelOptionIntValue(options, \"width\"); ok {\n\t\tresult.Width = width\n\t}\n\tif height, ok := modelOptionIntValue(options, \"height\"); ok {\n\t\tresult.Height = height\n\t}\n\tif strategy := modelOptionString(options, \"strategy\"); strategy != \"\" {\n\t\tresult.Strategy = strategy\n\t}\n\tif resizeOptions, ok := options[\"resize_options\"].(map[string]any); ok {\n\t\tif width, ok := modelOptionIntValue(resizeOptions, \"width\"); ok {\n\t\t\tresult.Width = width\n\t\t}\n\t\tif height, ok := modelOptionIntValue(resizeOptions, \"height\"); ok {\n\t\t\tresult.Height = height\n\t\t}\n\t\tif strategy := modelOptionString(resizeOptions, \"strategy\"); strategy != \"\" {\n\t\t\tresult.Strategy = strategy\n\t\t}\n\t}\n\treturn &result\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"http_options\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleHTTPOptionsFromModelOption(value any) *genai.HTTPOptions {\n\toptions, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\tresult := &genai.HTTPOptions{}\n\tif baseURL, ok := options[\"base_url\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t} else if baseURL, ok := options[\"baseUrl\"].(string); ok {\n\t\tresult.BaseURL = baseURL\n\t}\n\tif apiVersion, ok := options[\"api_version\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t} else if apiVersion, ok := options[\"apiVersion\"].(string); ok {\n\t\tresult.APIVersion = apiVersion\n\t}\n\tif timeout, ok := modelOptionDurationMilliseconds(options, \"timeout\"); ok {\n\t\tresult.Timeout = &timeout\n\t} else if timeout, ok := modelOptionDurationMilliseconds(options, \"timeout_ms\"); ok {\n\t\tresult.Timeout = &timeout\n\t}\n\tresult.Headers = googleLLMHTTPHeadersFromModelOption(options[\"headers\"])\n\tif extraBody, ok := options[\"extra_body\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t} else if extraBody, ok := options[\"extraBody\"].(map[string]any); ok {\n\t\tresult.ExtraBody = cloneAppAnyMap(extraBody)\n\t}\n\tif result.BaseURL == \"\" && result.APIVersion == \"\" && result.Timeout == nil && len(result.Headers) == 0 && len(result.ExtraBody) == 0 {\n\t\treturn nil\n\t}\n\treturn result\n}\n\nfunc modelOptionDurationMilliseconds(options map[string]any, key string) (time.Duration, bool) {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn time.Duration(value) * time.Millisecond, true\n}\n\nfunc googleLLMHTTPHeadersFromModelOption(value any) http.Header {\n\theaders := http.Header{}\n\traw, ok := value.(map[string]any)\n\tif !ok {\n\t\treturn headers\n\t}\n\tfor key, value := range raw {\n\t\tvalues := googleLLMHTTPHeaderValues(value)\n\t\tif len(values) > 0 {\n\t\t\theaders[key] = values\n\t\t}\n\t}\n\treturn headers\n}\n\nfunc googleLLMHTTPHeaderValues(value any) []string {\n\tswitch typed := value.(type) {\n\tcase string:\n\t\tif typed == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\treturn []string{typed}\n\tcase []any:\n\t\tvalues := make([]string, 0, len(typed))\n\t\tfor _, item := range typed {\n\t\t\tstr, ok := item.(string)\n\t\t\tif ok && str != \"\" {\n\t\t\t\tvalues = append(values, str)\n\t\t\t}\n\t\t}\n\t\treturn values\n\tcase []string:\n\t\treturn append([]string(nil), typed...)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "3ce034c51e281381d7a08e1209ecb4a26a7998da": {"t": 1785755417.4447112, "n": 321754, "h": "a1177c8e55e7df71", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n\t\"google.golang.org/genai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlocation string\n\tvoice string\n\tgender string\n\tcloneKey string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleLLMConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n}\n\nfunc (c appGoogleLLMConfig) options() []adaptergoogle.GoogleLLMOption {\n\topts := []adaptergoogle.GoogleLLMOption{}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMProject(c.project))\n\t}\n\tif c.location != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleLLMLocation(c.location))\n\t}\n\treturn opts\n}\n\ntype appGoogleRealtimeConfig struct {\n\tvertexAI *bool\n\tproject string\n\tlocation string\n\tvoice string\n\tlanguage string\n\tmodalities []string\n\tturnDetection *bool\n\tinputAudioTranscription *bool\n\toutputAudioTranscription *bool\n\ttemperature *float64\n\tmaxOutputTokens int\n\ttopP *float64\n\ttopK int\n\tproactivity *bool\n\taffectiveDialog *bool\n\tapiVersion string\n\trealtimeInputConfig *genai.RealtimeInputConfig\n\tcontextWindowCompression *genai.ContextWindowCompressionConfig\n\tthinkingConfig *genai.ThinkingConfig\n\tmediaResolution genai.MediaResolution\n\tsessionResumptionHandle string\n\tconnectOptions *llm.APIConnectOptions\n\ttoolResponseScheduling genai.FunctionResponseScheduling\n\ttoolBehavior genai.Behavior\n}\n\nfunc (c appGoogleRealtimeConfig) options(model string) []adaptergoogle.GoogleRealtimeOption {\n\topts := []adaptergoogle.GoogleRealtimeOption{adaptergoogle.WithGoogleRealtimeModel(model)}\n\tif c.vertexAI != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVertexAI(*c.vertexAI))\n\t}\n\tif c.project != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProject(c.project))\n\t}\n\tif c.location != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLocation(c.location))\n\t}\n\tif c.voice != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeVoice(c.voice))\n\t}\n\tif c.language != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeLanguage(c.language))\n\t}\n\tif len(c.modalities) > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeModalities(c.modalities))\n\t}\n\tif c.turnDetection != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTurnDetection(*c.turnDetection))\n\t}\n\tif c.inputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputAudioTranscription(*c.inputAudioTranscription))\n\t}\n\tif c.outputAudioTranscription != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeOutputAudioTranscription(*c.outputAudioTranscription))\n\t}\n\tif c.temperature != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTemperature(*c.temperature))\n\t}\n\tif c.maxOutputTokens > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMaxOutputTokens(c.maxOutputTokens))\n\t}\n\tif c.topP != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopP(*c.topP))\n\t}\n\tif c.topK > 0 {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeTopK(c.topK))\n\t}\n\tif c.proactivity != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeProactivity(*c.proactivity))\n\t}\n\tif c.affectiveDialog != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAffectiveDialog(*c.affectiveDialog))\n\t}\n\tif c.apiVersion != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeAPIVersion(c.apiVersion))\n\t}\n\tif c.realtimeInputConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeInputConfig(c.realtimeInputConfig))\n\t}\n\tif c.contextWindowCompression != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeContextWindowCompression(c.contextWindowCompression))\n\t}\n\tif c.thinkingConfig != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeThinkingConfig(c.thinkingConfig))\n\t}\n\tif c.mediaResolution != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeMediaResolution(c.mediaResolution))\n\t}\n\tif c.sessionResumptionHandle != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeSessionResumptionHandle(c.sessionResumptionHandle))\n\t}\n\tif c.connectOptions != nil {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeConnectOptions(*c.connectOptions))\n\t}\n\tif c.toolResponseScheduling != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolResponseScheduling(c.toolResponseScheduling))\n\t}\n\tif c.toolBehavior != \"\" {\n\t\topts = append(opts, adaptergoogle.WithGoogleRealtimeToolBehavior(c.toolBehavior))\n\t}\n\treturn opts\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleLLM = func(apiKey string, model string, cfg appGoogleLLMConfig) (llm.LLM, error) {\n\treturn adaptergoogle.NewGoogleLLM(apiKey, model, cfg.options()...)\n}\n\nvar appNewGoogleRealtime = func(apiKey string, model string, cfg appGoogleRealtimeConfig) (llm.RealtimeModel, error) {\n\treturn adaptergoogle.NewRealtimeModel(apiKey, cfg.options(model)...)\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.location != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.location != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.gender != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKey != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlocation: cfg.TTSRegion,\n\t\tvoice: cfg.TTSVoice,\n\t\tgender: cfg.TTSGender,\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tcustom, _ := options[\"custom_pronunciations\"].(*texttospeechpb.CustomPronunciations)\n\treturn custom\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel, googleLLMConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn appNewGoogleRealtime(cfg.GoogleAPIKey, cfg.RealtimeModel, googleRealtimeConfigFromAppConfig(cfg))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nfunc googleLLMConfigFromAppConfig(cfg AppConfig) appGoogleLLMConfig {\n\tgoogleCfg := appGoogleLLMConfig{}\n\tif vertexAI := modelOptionBool(cfg.LLMModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.LLMModelOptions, \"project\")\n\tgoogleCfg.location = modelOptionString(cfg.LLMModelOptions, \"location\")\n\treturn googleCfg\n}\n\nfunc googleRealtimeConfigFromAppConfig(cfg AppConfig) appGoogleRealtimeConfig {\n\tgoogleCfg := appGoogleRealtimeConfig{}\n\tif vertexAI := modelOptionBool(cfg.RealtimeModelOptions, \"vertexai\"); vertexAI != nil {\n\t\tgoogleCfg.vertexAI = vertexAI\n\t}\n\tgoogleCfg.project = modelOptionString(cfg.RealtimeModelOptions, \"project\")\n\tgoogleCfg.location = modelOptionString(cfg.RealtimeModelOptions, \"location\")\n\tgoogleCfg.voice = modelOptionString(cfg.RealtimeModelOptions, \"voice\")\n\tgoogleCfg.language = modelOptionString(cfg.RealtimeModelOptions, \"language\")\n\tgoogleCfg.modalities = modelOptionStringList(cfg.RealtimeModelOptions, \"modalities\")\n\tif turnDetection := modelOptionBool(cfg.RealtimeModelOptions, \"turn_detection\"); turnDetection != nil {\n\t\tgoogleCfg.turnDetection = turnDetection\n\t}\n\tif inputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"input_audio_transcription\"); inputAudioTranscription != nil {\n\t\tgoogleCfg.inputAudioTranscription = inputAudioTranscription\n\t}\n\tif outputAudioTranscription := modelOptionBool(cfg.RealtimeModelOptions, \"output_audio_transcription\"); outputAudioTranscription != nil {\n\t\tgoogleCfg.outputAudioTranscription = outputAudioTranscription\n\t}\n\tgoogleCfg.temperature = modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\")\n\tgoogleCfg.maxOutputTokens = modelOptionInt(cfg.RealtimeModelOptions, \"max_output_tokens\")\n\tgoogleCfg.topP = modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\")\n\tgoogleCfg.topK = modelOptionInt(cfg.RealtimeModelOptions, \"top_k\")\n\tif proactivity := modelOptionBool(cfg.RealtimeModelOptions, \"proactivity\"); proactivity != nil {\n\t\tgoogleCfg.proactivity = proactivity\n\t}\n\tif affectiveDialog := modelOptionBool(cfg.RealtimeModelOptions, \"enable_affective_dialog\"); affectiveDialog != nil {\n\t\tgoogleCfg.affectiveDialog = affectiveDialog\n\t}\n\tgoogleCfg.apiVersion = modelOptionString(cfg.RealtimeModelOptions, \"api_version\")\n\tgoogleCfg.realtimeInputConfig = googleRealtimeInputConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.contextWindowCompression = googleRealtimeContextWindowCompressionFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.thinkingConfig = googleRealtimeThinkingConfigFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.mediaResolution = genai.MediaResolution(modelOptionString(cfg.RealtimeModelOptions, \"media_resolution\"))\n\tgoogleCfg.sessionResumptionHandle = modelOptionString(cfg.RealtimeModelOptions, \"session_resumption_handle\")\n\tgoogleCfg.connectOptions = googleRealtimeConnectOptionsFromOptions(cfg.RealtimeModelOptions)\n\tgoogleCfg.toolResponseScheduling = genai.FunctionResponseScheduling(modelOptionString(cfg.RealtimeModelOptions, \"tool_response_scheduling\"))\n\tgoogleCfg.toolBehavior = genai.Behavior(modelOptionString(cfg.RealtimeModelOptions, \"tool_behavior\"))\n\treturn googleCfg\n}\n\nfunc googleRealtimeConnectOptionsFromOptions(options map[string]any) *llm.APIConnectOptions {\n\tconnectOptions := llm.DefaultAPIConnectOptions()\n\tconfigured := false\n\tif maxRetry, ok := modelOptionIntValue(options, \"connect_max_retry\"); ok {\n\t\tconnectOptions.MaxRetry = maxRetry\n\t\tconfigured = true\n\t}\n\tif timeoutMS, ok := modelOptionIntValue(options, \"connect_timeout_ms\"); ok {\n\t\tconnectOptions.Timeout = time.Duration(timeoutMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif retryIntervalMS, ok := modelOptionIntValue(options, \"connect_retry_interval_ms\"); ok {\n\t\tconnectOptions.RetryInterval = time.Duration(retryIntervalMS) * time.Millisecond\n\t\tconfigured = true\n\t}\n\tif !configured {\n\t\treturn nil\n\t}\n\treturn &connectOptions\n}\n\nfunc googleRealtimeContextWindowCompressionFromOptions(options map[string]any) *genai.ContextWindowCompressionConfig {\n\tvar config genai.ContextWindowCompressionConfig\n\tif trigger, ok := modelOptionIntValue(options, \"context_window_trigger_tokens\"); ok {\n\t\tvalue := int64(trigger)\n\t\tconfig.TriggerTokens = &value\n\t}\n\tif target, ok := modelOptionIntValue(options, \"context_window_target_tokens\"); ok {\n\t\tvalue := int64(target)\n\t\tconfig.SlidingWindow = &genai.SlidingWindow{TargetTokens: &value}\n\t}\n\tif config.TriggerTokens == nil && config.SlidingWindow == nil {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeThinkingConfigFromOptions(options map[string]any) *genai.ThinkingConfig {\n\tvar config genai.ThinkingConfig\n\tif budget, ok := modelOptionIntValue(options, \"thinking_budget\"); ok {\n\t\tvalue := int32(budget)\n\t\tconfig.ThinkingBudget = &value\n\t}\n\tif includeThoughts := modelOptionBool(options, \"include_thoughts\"); includeThoughts != nil {\n\t\tconfig.IncludeThoughts = *includeThoughts\n\t}\n\tif level := modelOptionString(options, \"thinking_level\"); level != \"\" {\n\t\tconfig.ThinkingLevel = genai.ThinkingLevel(level)\n\t}\n\tif config.ThinkingBudget == nil && !config.IncludeThoughts && config.ThinkingLevel == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nfunc googleRealtimeInputConfigFromOptions(options map[string]any) *genai.RealtimeInputConfig {\n\tvar config genai.RealtimeInputConfig\n\tif disabled := modelOptionBool(options, \"automatic_activity_detection_disabled\"); disabled != nil {\n\t\tconfig.AutomaticActivityDetection = &genai.AutomaticActivityDetection{Disabled: *disabled}\n\t}\n\tif activityHandling := modelOptionString(options, \"activity_handling\"); activityHandling != \"\" {\n\t\tconfig.ActivityHandling = genai.ActivityHandling(activityHandling)\n\t}\n\tif config.AutomaticActivityDetection == nil && config.ActivityHandling == \"\" {\n\t\treturn nil\n\t}\n\treturn &config\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "440576326782bfe3406e379c95bc6b80875ab0e5": {"t": 1785755419.5469534, "n": 311049, "h": "adb4ace210d89eeb", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlocation string\n\tvoice string\n\tgender string\n\tcloneKey string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.location != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.location != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.gender != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKey != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlocation: cfg.TTSRegion,\n\t\tvoice: cfg.TTSVoice,\n\t\tgender: cfg.TTSGender,\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tcustom, _ := options[\"custom_pronunciations\"].(*texttospeechpb.CustomPronunciations)\n\treturn custom\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewRealtimeModel(cfg.GoogleAPIKey, adaptergoogle.WithGoogleRealtimeModel(cfg.RealtimeModel))\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "fd732014cdb1ea0ab5b29649ac3a7635d93beded": {"t": 1785755422.00074, "n": 310911, "h": "b19ae377c6801b30", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tspeechpb \"cloud.google.com/go/speech/apiv1/speechpb\"\n\tspeechv2pb \"cloud.google.com/go/speech/apiv2/speechpb\"\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlocation string\n\tvoice string\n\tgender string\n\tcloneKey string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n\tdenoiserConfig *speechv2pb.DenoiserConfig\n\tadaptation *speechpb.SpeechAdaptation\n\tadaptationV2 *speechv2pb.SpeechAdaptation\n\tendpointingSensitivity string\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.location != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\tif cfg.denoiserConfig != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDenoiserConfig(cfg.denoiserConfig))\n\t}\n\tif cfg.adaptation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptation(cfg.adaptation))\n\t}\n\tif cfg.adaptationV2 != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAdaptationV2(cfg.adaptationV2))\n\t}\n\tif cfg.endpointingSensitivity != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTEndpointingSensitivity(cfg.endpointingSensitivity))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.location != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.gender != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKey != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t\tdenoiserConfig: googleSTTDenoiserConfigFromOptions(cfg.STTModelOptions),\n\t\tadaptation: googleSTTAdaptationFromOptions(cfg.STTModelOptions),\n\t\tadaptationV2: googleSTTAdaptationV2FromOptions(cfg.STTModelOptions),\n\t\tendpointingSensitivity: modelOptionString(cfg.STTModelOptions, \"endpointing_sensitivity\"),\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTDenoiserConfigFromOptions(options map[string]any) *speechv2pb.DenoiserConfig {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif denoiser, ok := options[\"denoiser_config\"].(*speechv2pb.DenoiserConfig); ok {\n\t\treturn denoiser\n\t}\n\tconfigOptions := options\n\tif nested, ok := options[\"denoiser_config\"].(map[string]any); ok {\n\t\tconfigOptions = nested\n\t}\n\tdenoiseAudio := modelOptionBool(configOptions, \"denoise_audio\")\n\tsnrThreshold := modelOptionFloat(configOptions, \"snr_threshold\")\n\tif denoiseAudio == nil && snrThreshold == nil {\n\t\treturn nil\n\t}\n\tconfig := &speechv2pb.DenoiserConfig{}\n\tif denoiseAudio != nil {\n\t\tconfig.DenoiseAudio = *denoiseAudio\n\t}\n\tif snrThreshold != nil {\n\t\tconfig.SnrThreshold = float32(*snrThreshold)\n\t}\n\treturn config\n}\n\nfunc googleSTTAdaptationFromOptions(options map[string]any) *speechpb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechpb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTAdaptationV2FromOptions(options map[string]any) *speechv2pb.SpeechAdaptation {\n\tif len(options) == 0 {\n\t\treturn nil\n\t}\n\tif adaptation, ok := options[\"adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\tif adaptation, ok := options[\"speech_adaptation\"].(*speechv2pb.SpeechAdaptation); ok {\n\t\treturn adaptation\n\t}\n\treturn nil\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlocation: cfg.TTSRegion,\n\t\tvoice: cfg.TTSVoice,\n\t\tgender: cfg.TTSGender,\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tcustom, _ := options[\"custom_pronunciations\"].(*texttospeechpb.CustomPronunciations)\n\treturn custom\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"stop_sequences\",\n\t\"candidate_count\",\n\t\"response_logprobs\",\n\t\"logprobs\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "72b2b4850f6a5fcdd159cc69f79a3d4c6c160031": {"t": 1785755429.2568586, "n": 308155, "h": "c8cc61a4533317b6", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\ttexttospeechpb \"cloud.google.com/go/texttospeech/apiv1/texttospeechpb\"\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tlocation string\n\tvoice string\n\tgender string\n\tcloneKey string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\tsampleRate *int\n\taudioEncoding *texttospeechpb.AudioEncoding\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n\tcustomPronunciations *texttospeechpb.CustomPronunciations\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tlocation string\n\tlanguage string\n\tstreaming *bool\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechStartTimeout time.Duration\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n\tkeywords []adaptergoogle.GoogleSTTKeyword\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.location != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLocation(cfg.location))\n\t}\n\tif cfg.language != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTLanguage(cfg.language))\n\t}\n\tif cfg.streaming != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTStreaming(*cfg.streaming))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechStartTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechStartTimeout(cfg.speechStartTimeout))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\tif len(cfg.keywords) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTKeywords(cfg.keywords...))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.location != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLocation(cfg.location))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.gender != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSGender(cfg.gender))\n\t}\n\tif cfg.cloneKey != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.audioEncoding != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSAudioEncoding(*cfg.audioEncoding))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\tif cfg.customPronunciations != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSCustomPronunciations(cfg.customPronunciations))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTSpeechStartTimeoutMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTStreaming *bool\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSGender string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTStreaming: getenvOptionalBool(\"RTP_AGENT_STT_STREAMING\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTSpeechStartTimeoutMS: getenvOptionalInt(\"RTP_AGENT_STT_SPEECH_START_TIMEOUT_MS\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSGender: os.Getenv(\"RTP_AGENT_TTS_GENDER\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tlocation: cfg.STTRegion,\n\t\tlanguage: cfg.STTLanguage,\n\t\tstreaming: cfg.STTStreaming,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t\tkeywords: googleSTTKeywordsFromConfig(cfg.STTKeywords),\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t} else if cfg.STTEndpointingSeconds != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingSeconds * float64(time.Second))\n\t}\n\tif cfg.STTSpeechStartTimeoutMS != nil {\n\t\tgoogleCfg.speechStartTimeout = time.Duration(*cfg.STTSpeechStartTimeoutMS) * time.Millisecond\n\t}\n\treturn googleCfg\n}\n\nfunc googleSTTKeywordsFromConfig(keywords []deepgram.DeepgramKeyword) []adaptergoogle.GoogleSTTKeyword {\n\tif len(keywords) == 0 {\n\t\treturn nil\n\t}\n\tgoogleKeywords := make([]adaptergoogle.GoogleSTTKeyword, 0, len(keywords))\n\tfor _, keyword := range keywords {\n\t\tif keyword.Keyword == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tgoogleKeywords = append(googleKeywords, adaptergoogle.GoogleSTTKeyword{\n\t\t\tValue: keyword.Keyword,\n\t\t\tBoost: float32(keyword.Boost),\n\t\t})\n\t}\n\treturn googleKeywords\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tlocation: cfg.TTSRegion,\n\t\tvoice: cfg.TTSVoice,\n\t\tgender: cfg.TTSGender,\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tgoogleCfg.audioEncoding = googleTTSAudioEncodingFromConfig(cfg)\n\tswitch {\n\tcase strings.EqualFold(cfg.TTSTextType, \"markup\"):\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\tcase strings.EqualFold(cfg.TTSTextType, \"ssml\"):\n\t\tssml := true\n\t\tgoogleCfg.ssml = &ssml\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\tgoogleCfg.customPronunciations = googleTTSCustomPronunciationsFromOptions(cfg.TTSModelOptions)\n\treturn googleCfg\n}\n\nfunc googleTTSAudioEncodingFromConfig(cfg AppConfig) *texttospeechpb.AudioEncoding {\n\tencoding := strings.TrimSpace(cfg.TTSEncoding)\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"audio_encoding\")\n\t}\n\tif encoding == \"\" {\n\t\tencoding = modelOptionString(cfg.TTSModelOptions, \"encoding\")\n\t}\n\tif encoding == \"\" {\n\t\treturn nil\n\t}\n\tnormalized := strings.ToUpper(strings.ReplaceAll(encoding, \"-\", \"_\"))\n\tif normalized == \"OGGOPUS\" {\n\t\tnormalized = \"OGG_OPUS\"\n\t}\n\tif value, ok := texttospeechpb.AudioEncoding_value[normalized]; ok {\n\t\taudioEncoding := texttospeechpb.AudioEncoding(value)\n\t\treturn &audioEncoding\n\t}\n\treturn nil\n}\n\nfunc googleTTSCustomPronunciationsFromOptions(options map[string]any) *texttospeechpb.CustomPronunciations {\n\tcustom, _ := options[\"custom_pronunciations\"].(*texttospeechpb.CustomPronunciations)\n\treturn custom\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = llmExtraParamsFromConfig(cfg)\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif toolChoice := googleLLMToolChoiceFromConfig(cfg); toolChoice != nil {\n\t\topts.ToolChoice = toolChoice\n\t}\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc llmExtraParamsFromConfig(cfg AppConfig) map[string]any {\n\tparams := cloneAppAnyMap(cfg.LLMExtraBody)\n\tif normalizeProvider(cfg.LLMProvider) == providerGoogle {\n\t\tfor _, key := range googleLLMExtraParamKeys {\n\t\t\tvalue, ok := cfg.LLMModelOptions[key]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, exists := params[key]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif params == nil {\n\t\t\t\tparams = make(map[string]any)\n\t\t\t}\n\t\t\tparams[key] = value\n\t\t}\n\t}\n\tif len(params) == 0 {\n\t\treturn nil\n\t}\n\treturn params\n}\n\nvar googleLLMExtraParamKeys = []string{\n\t\"cached_content\",\n\t\"temperature\",\n\t\"top_p\",\n\t\"top_k\",\n\t\"presence_penalty\",\n\t\"frequency_penalty\",\n\t\"max_output_tokens\",\n\t\"seed\",\n\t\"response_mime_type\",\n\t\"response_schema\",\n\t\"response_json_schema\",\n\t\"routing_config\",\n\t\"model_selection_config\",\n\t\"labels\",\n\t\"model_armor_config\",\n\t\"enable_enhanced_civic_answers\",\n\t\"image_config\",\n\t\"response_modalities\",\n\t\"speech_config\",\n\t\"audio_timestamp\",\n\t\"service_tier\",\n\t\"thinking_config\",\n\t\"safety_settings\",\n\t\"media_resolution\",\n\t\"tool_config\",\n\t\"retrieval_config\",\n}\n\nfunc googleLLMToolChoiceFromConfig(cfg AppConfig) llm.ToolChoice {\n\tif normalizeProvider(cfg.LLMProvider) != providerGoogle {\n\t\treturn nil\n\t}\n\ttoolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\")\n\tif toolChoice == \"\" {\n\t\treturn nil\n\t}\n\treturn llm.ToolChoice(toolChoice)\n}\n\nfunc cloneAppAnyMap(values map[string]any) map[string]any {\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tclone := make(map[string]any, len(values))\n\tfor key, value := range values {\n\t\tclone[key] = value\n\t}\n\treturn clone\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "18db1de0274ff92d06de648fb2ec555be4ab30cc": {"t": 1785755432.154103, "n": 302429, "h": "3cae929f5504c86f", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tvoice string\n\tcloneKey string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\tsampleRate *int\n\teffectsProfileID string\n\tvolumeGainDB float64\n\tstreaming *bool\n\tssml *bool\n\tmarkup *bool\n}\n\ntype appGoogleSTTConfig struct {\n\tmodel string\n\tsampleRate *int\n\tpunctuate *bool\n\tspokenPunctuation *bool\n\tprofanityFilter *bool\n\tdetectLanguage *bool\n\tinterimResults *bool\n\twordTimeOffsets *bool\n\twordConfidence *bool\n\tspeechEndTimeout time.Duration\n\tminConfidence *float64\n\tvoiceActivityEvents *bool\n\talternativeLanguages []string\n}\n\nvar appNewGoogleSTT = func(credentialsFile string, cfg appGoogleSTTConfig) (corestt.STT, error) {\n\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\tif cfg.model != \"\" {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.model))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.punctuate != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.punctuate))\n\t}\n\tif cfg.spokenPunctuation != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.spokenPunctuation))\n\t}\n\tif cfg.profanityFilter != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.profanityFilter))\n\t}\n\tif cfg.detectLanguage != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTDetectLanguage(*cfg.detectLanguage))\n\t}\n\tif cfg.interimResults != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTInterimResults(*cfg.interimResults))\n\t}\n\tif cfg.wordTimeOffsets != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordTimeOffsets(*cfg.wordTimeOffsets))\n\t}\n\tif cfg.wordConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTWordConfidence(*cfg.wordConfidence))\n\t}\n\tif cfg.speechEndTimeout > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpeechEndTimeout(cfg.speechEndTimeout))\n\t}\n\tif cfg.minConfidence != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTMinConfidenceThreshold(*cfg.minConfidence))\n\t}\n\tif cfg.voiceActivityEvents != nil {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTVoiceActivityEvents(*cfg.voiceActivityEvents))\n\t}\n\tif len(cfg.alternativeLanguages) > 0 {\n\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTAlternativeLanguages(cfg.alternativeLanguages...))\n\t}\n\treturn adaptergoogle.NewGoogleSTT(credentialsFile, sttOpts...)\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.cloneKey != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoiceCloneKey(cfg.cloneKey))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.sampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSampleRate(int32(*cfg.sampleRate)))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\tif cfg.streaming != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSStreaming(*cfg.streaming))\n\t}\n\tif cfg.ssml != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSSML(*cfg.ssml))\n\t}\n\tif cfg.markup != nil {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSMarkup(*cfg.markup))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTWordConfidence *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTMinConfidenceThreshold *float64\n\tSTTVoiceActivityEvents *bool\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSStreaming *bool\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTWordConfidence: getenvOptionalBool(\"RTP_AGENT_STT_WORD_CONFIDENCE\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTMinConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_MIN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTVoiceActivityEvents: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_ACTIVITY_EVENTS\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSStreaming: getenvOptionalBool(\"RTP_AGENT_TTS_STREAMING\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleSTTConfigFromAppConfig(cfg AppConfig) appGoogleSTTConfig {\n\tgoogleCfg := appGoogleSTTConfig{\n\t\tmodel: cfg.STTModel,\n\t\tsampleRate: cfg.STTSampleRate,\n\t\tpunctuate: cfg.STTPunctuate,\n\t\tspokenPunctuation: cfg.STTSpokenPunctuation,\n\t\tprofanityFilter: cfg.STTProfanityFilter,\n\t\tdetectLanguage: cfg.STTLanguageDetection,\n\t\tinterimResults: cfg.STTInterimResults,\n\t\twordTimeOffsets: cfg.STTWordTimestamps,\n\t\twordConfidence: cfg.STTWordConfidence,\n\t\tminConfidence: cfg.STTMinConfidenceThreshold,\n\t\tvoiceActivityEvents: cfg.STTVoiceActivityEvents,\n\t\talternativeLanguages: splitStringList(cfg.STTLanguageOptions),\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tgoogleCfg.speechEndTimeout = time.Duration(*cfg.STTEndpointingMS) * time.Millisecond\n\t}\n\treturn googleCfg\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tvoice: cfg.TTSVoice,\n\t\tcloneKey: cfg.TTSVoiceID,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t\tsampleRate: cfg.TTSSampleRate,\n\t\tstreaming: cfg.TTSStreaming,\n\t\tssml: cfg.TTSEnableSSMLParsing,\n\t}\n\tif strings.EqualFold(cfg.TTSTextType, \"markup\") {\n\t\tmarkup := true\n\t\tgoogleCfg.markup = &markup\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\treturn googleCfg\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleSTT(cfg.GoogleCredentialsFile, googleSTTConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = cfg.LLMExtraBody\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "c950561b38e533bab95b670ef5b08d898c970e8d": {"t": 1785755435.6155007, "n": 298490, "h": "7c7f67628f222eeb", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tvoice string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\teffectsProfileID string\n\tvolumeGainDB float64\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\tRealtimeGenerateReplyTimeoutSeconds *float64\n\tRealtimeModelOptions map[string]any\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tRealtimeGenerateReplyTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_REALTIME_GENERATE_REPLY_TIMEOUT_SECONDS\"),\n\t\tRealtimeModelOptions: splitEnvMap(\"RTP_AGENT_REALTIME_MODEL_OPTIONS\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc awsLLMFromConfig(cfg AppConfig) (*adapteraws.AWSLLM, error) {\n\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel, awsLLMOptionsFromConfig(cfg)...)\n}\n\nfunc awsLLMOptionsFromConfig(cfg AppConfig) []adapteraws.AWSLLMOption {\n\tllmOpts := []adapteraws.AWSLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMMaxOutputTokens(int32(maxOutputTokens)))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTemperature(float32(*temperature)))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMTopP(float32(*topP)))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif fields, ok := cfg.LLMModelOptions[\"additional_request_fields\"]; ok {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMAdditionalRequestFields(fields))\n\t}\n\tif cacheSystem := modelOptionBool(cfg.LLMModelOptions, \"cache_system\"); cacheSystem != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheSystem(*cacheSystem))\n\t}\n\tif cacheTools := modelOptionBool(cfg.LLMModelOptions, \"cache_tools\"); cacheTools != nil {\n\t\tllmOpts = append(llmOpts, adapteraws.WithAWSLLMCacheTools(*cacheTools))\n\t}\n\treturn llmOpts\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsLLMFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguage(awstranscribetypes.LanguageCode(cfg.STTLanguage)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tvoice: cfg.TTSVoice,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\treturn googleCfg\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.STTPunctuate))\n\t\t}\n\t\tif cfg.STTSpokenPunctuation != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.STTSpokenPunctuation))\n\t\t}\n\t\tif cfg.STTProfanityFilter != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t\t}\n\t\tprovider, err := adaptergoogle.NewGoogleSTT(cfg.GoogleCredentialsFile, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := awsRealtimeOptionsFromConfig(cfg)\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\tif cfg.RealtimeGenerateReplyTimeoutSeconds != nil {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeGenerateReplyTimeout(time.Duration(*cfg.RealtimeGenerateReplyTimeoutSeconds*float64(time.Second))))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc awsRealtimeOptionsFromConfig(cfg AppConfig) []adapteraws.AWSRealtimeOption {\n\topts := []adapteraws.AWSRealtimeOption{\n\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t}\n\tif maxTokens := modelOptionInt(cfg.RealtimeModelOptions, \"max_tokens\"); maxTokens > 0 {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeMaxTokens(maxTokens))\n\t}\n\tif topP := modelOptionFloat(cfg.RealtimeModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTopP(*topP))\n\t}\n\tif temperature := modelOptionFloat(cfg.RealtimeModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeTemperature(*temperature))\n\t}\n\tif toolChoice := modelOptionString(cfg.RealtimeModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, adapteraws.WithAWSRealtimeToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\treturn opts\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = cfg.LLMExtraBody\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "44da9fdf065a563fdde44ba4926c4b222ee0ff65": {"t": 1785755438.2582493, "n": 295596, "h": "cf01d4861408b949", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tvoice string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\teffectsProfileID string\n\tvolumeGainDB float64\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\tRealtimeVoice string\n\tRealtimeTurnDetection string\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tRealtimeVoice: os.Getenv(\"RTP_AGENT_REALTIME_VOICE\"),\n\t\tRealtimeTurnDetection: os.Getenv(\"RTP_AGENT_REALTIME_TURN_DETECTION\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tvoice: cfg.TTSVoice,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\treturn googleCfg\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.STTPunctuate))\n\t\t}\n\t\tif cfg.STTSpokenPunctuation != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.STTSpokenPunctuation))\n\t\t}\n\t\tif cfg.STTProfanityFilter != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t\t}\n\t\tprovider, err := adaptergoogle.NewGoogleSTT(cfg.GoogleCredentialsFile, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\topts := []adapteraws.AWSRealtimeOption{\n\t\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t\t}\n\t\tif cfg.RealtimeVoice != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeVoice(cfg.RealtimeVoice))\n\t\t}\n\t\tif cfg.RealtimeTurnDetection != \"\" {\n\t\t\topts = append(opts, adapteraws.WithAWSRealtimeTurnDetection(cfg.RealtimeTurnDetection))\n\t\t}\n\t\treturn adapteraws.NewAWSRealtimeModel(cfg.RealtimeModel, opts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = cfg.LLMExtraBody\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "4ed03746fa37c0f8b1afb280f835e475db16eb6a": {"t": 1785755441.5002222, "n": 295036, "h": "68a6587c1dd6e6e6", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tvoice string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\teffectsProfileID string\n\tvolumeGainDB float64\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tvoice: cfg.TTSVoice,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\treturn googleCfg\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.STTPunctuate))\n\t\t}\n\t\tif cfg.STTSpokenPunctuation != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.STTSpokenPunctuation))\n\t\t}\n\t\tif cfg.STTProfanityFilter != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t\t}\n\t\tprovider, err := adaptergoogle.NewGoogleSTT(cfg.GoogleCredentialsFile, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tcase providerAWS:\n\t\treturn adapteraws.NewAWSRealtimeModel(\n\t\t\tcfg.RealtimeModel,\n\t\t\tadapteraws.WithAWSRealtimeRegion(cfg.AWSRegion),\n\t\t), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = cfg.LLMExtraBody\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "4f1c6cde652ff364c9cffb4b621fc27c0c11cf19": {"t": 1785755443.730641, "n": 294893, "h": "686d9ca25f7f5a29", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tvoice string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\teffectsProfileID string\n\tvolumeGainDB float64\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tvoice: cfg.TTSVoice,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\treturn googleCfg\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.STTPunctuate))\n\t\t}\n\t\tif cfg.STTSpokenPunctuation != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.STTSpokenPunctuation))\n\t\t}\n\t\tif cfg.STTProfanityFilter != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t\t}\n\t\tprovider, err := adaptergoogle.NewGoogleSTT(cfg.GoogleCredentialsFile, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tspeakerActiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_active_format\")\n\t\tspeakerPassiveFormat := modelOptionString(cfg.STTModelOptions, \"speaker_passive_format\")\n\t\tif speakerActiveFormat != \"\" || speakerPassiveFormat != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFormats(speakerActiveFormat, speakerPassiveFormat))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = cfg.LLMExtraBody\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}, "320f92c889c71929aff7ac043fbe6f5a0c6f001d": {"t": 1785755449.7802503, "n": 294159, "h": "2139a96c17f62569", "c": "package app\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tawspollytypes \"github.com/aws/aws-sdk-go-v2/service/polly/types\"\n\tawstranscribetypes \"github.com/aws/aws-sdk-go-v2/service/transcribestreaming/types\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/anthropic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/assemblyai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/asyncai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatario\"\n\t\"github.com/cavos-io/rtp-agent/adapter/avatartalk\"\n\tadapteraws \"github.com/cavos-io/rtp-agent/adapter/aws\"\n\t\"github.com/cavos-io/rtp-agent/adapter/azure\"\n\t\"github.com/cavos-io/rtp-agent/adapter/baseten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bey\"\n\t\"github.com/cavos-io/rtp-agent/adapter/bithuman\"\n\t\"github.com/cavos-io/rtp-agent/adapter/blingfire\"\n\t\"github.com/cavos-io/rtp-agent/adapter/browser\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cambai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cartesia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cavos\"\n\t\"github.com/cavos-io/rtp-agent/adapter/cerebras\"\n\t\"github.com/cavos-io/rtp-agent/adapter/clova\"\n\t\"github.com/cavos-io/rtp-agent/adapter/deepgram\"\n\t\"github.com/cavos-io/rtp-agent/adapter/did\"\n\t\"github.com/cavos-io/rtp-agent/adapter/elevenlabs\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fal\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fireworksai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/fishaudio\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gladia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gnani\"\n\tadaptergoogle \"github.com/cavos-io/rtp-agent/adapter/google\"\n\t\"github.com/cavos-io/rtp-agent/adapter/gradium\"\n\t\"github.com/cavos-io/rtp-agent/adapter/groq\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hamming\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hedra\"\n\t\"github.com/cavos-io/rtp-agent/adapter/hume\"\n\t\"github.com/cavos-io/rtp-agent/adapter/inworld\"\n\t\"github.com/cavos-io/rtp-agent/adapter/keyframe\"\n\t\"github.com/cavos-io/rtp-agent/adapter/krisp\"\n\t\"github.com/cavos-io/rtp-agent/adapter/langchain\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lemonslice\"\n\t\"github.com/cavos-io/rtp-agent/adapter/liveavatar\"\n\tadapterlivekit \"github.com/cavos-io/rtp-agent/adapter/livekit\"\n\t\"github.com/cavos-io/rtp-agent/adapter/lmnt\"\n\t\"github.com/cavos-io/rtp-agent/adapter/minimax\"\n\t\"github.com/cavos-io/rtp-agent/adapter/mistralai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/murf\"\n\t\"github.com/cavos-io/rtp-agent/adapter/neuphonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nltk\"\n\t\"github.com/cavos-io/rtp-agent/adapter/nvidia\"\n\t\"github.com/cavos-io/rtp-agent/adapter/openai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/perplexity\"\n\t\"github.com/cavos-io/rtp-agent/adapter/phonic\"\n\t\"github.com/cavos-io/rtp-agent/adapter/pipecat\"\n\t\"github.com/cavos-io/rtp-agent/adapter/resemble\"\n\t\"github.com/cavos-io/rtp-agent/adapter/respeecher\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rime\"\n\t\"github.com/cavos-io/rtp-agent/adapter/rtzr\"\n\t\"github.com/cavos-io/rtp-agent/adapter/runway\"\n\t\"github.com/cavos-io/rtp-agent/adapter/sarvam\"\n\t\"github.com/cavos-io/rtp-agent/adapter/silero\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simli\"\n\t\"github.com/cavos-io/rtp-agent/adapter/simplismart\"\n\t\"github.com/cavos-io/rtp-agent/adapter/slng\"\n\t\"github.com/cavos-io/rtp-agent/adapter/smallestai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/soniox\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechify\"\n\t\"github.com/cavos-io/rtp-agent/adapter/speechmatics\"\n\t\"github.com/cavos-io/rtp-agent/adapter/spitch\"\n\t\"github.com/cavos-io/rtp-agent/adapter/tavus\"\n\t\"github.com/cavos-io/rtp-agent/adapter/telnyx\"\n\t\"github.com/cavos-io/rtp-agent/adapter/ten\"\n\t\"github.com/cavos-io/rtp-agent/adapter/trugen\"\n\t\"github.com/cavos-io/rtp-agent/adapter/upliftai\"\n\t\"github.com/cavos-io/rtp-agent/adapter/xai\"\n\t\"github.com/cavos-io/rtp-agent/core/agent\"\n\t\"github.com/cavos-io/rtp-agent/core/audio/model\"\n\tbeta \"github.com/cavos-io/rtp-agent/core/beta\"\n\tbetatools \"github.com/cavos-io/rtp-agent/core/beta/tools\"\n\t\"github.com/cavos-io/rtp-agent/core/beta/workflows\"\n\t\"github.com/cavos-io/rtp-agent/core/evals\"\n\t\"github.com/cavos-io/rtp-agent/core/llm\"\n\tcorestt \"github.com/cavos-io/rtp-agent/core/stt\"\n\tcoretts \"github.com/cavos-io/rtp-agent/core/tts\"\n\tcorevad \"github.com/cavos-io/rtp-agent/core/vad\"\n\t\"github.com/cavos-io/rtp-agent/interface/worker\"\n\tworkeragora \"github.com/cavos-io/rtp-agent/interface/worker/agora\"\n\tworkerlivekit \"github.com/cavos-io/rtp-agent/interface/worker/livekit\"\n\tlogutil \"github.com/cavos-io/rtp-agent/library/logger\"\n\t\"github.com/cavos-io/rtp-agent/library/plugin\"\n\t\"github.com/cavos-io/rtp-agent/library/telemetry\"\n\t\"github.com/cavos-io/rtp-agent/library/tokenize\"\n\t\"github.com/livekit/protocol/livekit\"\n\tlivekitlogger \"github.com/livekit/protocol/logger\"\n\tlksdk \"github.com/livekit/server-sdk-go/v2\"\n\tgoopenai \"github.com/sashabaranov/go-openai\"\n)\n\nvar appNewAgoraChannelClient = workeragora.NewSDKChannelClient\nvar appNewAgoraDataPublisher = workeragora.NewSDKDataPublisher\n\ntype appGoogleTTSConfig struct {\n\tlanguage string\n\tvoice string\n\tmodel string\n\tprompt string\n\tspeakingRate float64\n\tpitch float64\n\teffectsProfileID string\n\tvolumeGainDB float64\n}\n\nvar appNewGoogleTTS = func(credentialsFile string, cfg appGoogleTTSConfig) (coretts.TTS, error) {\n\tttsOpts := []adaptergoogle.GoogleTTSOption{}\n\tif cfg.language != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSLanguage(cfg.language))\n\t}\n\tif cfg.voice != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVoice(cfg.voice))\n\t}\n\tif cfg.model != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSModel(cfg.model))\n\t}\n\tif cfg.prompt != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPrompt(cfg.prompt))\n\t}\n\tif cfg.speakingRate != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSSpeakingRate(cfg.speakingRate))\n\t}\n\tif cfg.pitch != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSPitch(cfg.pitch))\n\t}\n\tif cfg.effectsProfileID != \"\" {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSEffectsProfileID(cfg.effectsProfileID))\n\t}\n\tif cfg.volumeGainDB != 0 {\n\t\tttsOpts = append(ttsOpts, adaptergoogle.WithGoogleTTSVolumeGainDB(cfg.volumeGainDB))\n\t}\n\treturn adaptergoogle.NewGoogleTTS(credentialsFile, ttsOpts...)\n}\n\nfunc init() {\n\tplugin.RegisterPluginMetadata(anam.PluginTitle, anam.PluginVersion, anam.PluginPackage)\n\tplugin.RegisterPluginMetadata(anthropic.PluginTitle, anthropic.PluginVersion, anthropic.PluginPackage)\n\tplugin.RegisterPluginMetadata(assemblyai.PluginTitle, assemblyai.PluginVersion, assemblyai.PluginPackage)\n\tplugin.RegisterPluginMetadata(asyncai.PluginTitle, asyncai.PluginVersion, asyncai.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatario.PluginTitle, avatario.PluginVersion, avatario.PluginPackage)\n\tplugin.RegisterPluginMetadata(avatartalk.PluginTitle, avatartalk.PluginVersion, avatartalk.PluginPackage)\n\tplugin.RegisterPluginMetadata(adapteraws.PluginTitle, adapteraws.PluginVersion, adapteraws.PluginPackage)\n\tplugin.RegisterPluginMetadata(azure.PluginTitle, azure.PluginVersion, azure.PluginPackage)\n\tplugin.RegisterPluginMetadata(baseten.PluginTitle, baseten.PluginVersion, baseten.PluginPackage)\n\tplugin.RegisterPluginMetadata(bey.PluginTitle, bey.PluginVersion, bey.PluginPackage)\n\tplugin.RegisterPluginMetadata(bithuman.PluginTitle, bithuman.PluginVersion, bithuman.PluginPackage)\n\tplugin.RegisterPluginDownloader(browser.PluginTitle, browser.PluginVersion, browser.PluginPackage, browser.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(cambai.PluginTitle, cambai.PluginVersion, cambai.PluginPackage)\n\tplugin.RegisterPluginMetadata(cartesia.PluginTitle, cartesia.PluginVersion, cartesia.PluginPackage)\n\tplugin.RegisterPluginMetadata(cavos.PluginTitle, cavos.PluginVersion, cavos.PluginPackage)\n\tplugin.RegisterPluginMetadata(cerebras.PluginTitle, cerebras.PluginVersion, cerebras.PluginPackage)\n\tplugin.RegisterPluginDownloader(clova.PluginTitle, clova.PluginVersion, clova.PluginPackage, clova.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(deepgram.PluginTitle, deepgram.PluginVersion, deepgram.PluginPackage)\n\tplugin.RegisterPluginMetadata(did.PluginTitle, did.PluginVersion, did.PluginPackage)\n\tplugin.RegisterPluginMetadata(elevenlabs.PluginTitle, elevenlabs.PluginVersion, elevenlabs.PluginPackage)\n\tplugin.RegisterPluginMetadata(fal.PluginTitle, fal.PluginVersion, fal.PluginPackage)\n\tplugin.RegisterPluginMetadata(fireworksai.PluginTitle, fireworksai.PluginVersion, fireworksai.PluginPackage)\n\tplugin.RegisterPluginMetadata(fishaudio.PluginTitle, fishaudio.PluginVersion, fishaudio.PluginPackage)\n\tplugin.RegisterPluginMetadata(gladia.PluginTitle, gladia.PluginVersion, gladia.PluginPackage)\n\tplugin.RegisterPluginMetadata(gnani.PluginTitle, gnani.PluginVersion, gnani.PluginPackage)\n\tplugin.RegisterPluginMetadata(adaptergoogle.PluginTitle, adaptergoogle.PluginVersion, adaptergoogle.PluginPackage)\n\tplugin.RegisterPluginMetadata(gradium.PluginTitle, gradium.PluginVersion, gradium.PluginPackage)\n\tplugin.RegisterPluginMetadata(groq.PluginTitle, groq.PluginVersion, groq.PluginPackage)\n\tplugin.RegisterPluginMetadata(hamming.PluginTitle, hamming.PluginVersion, hamming.PluginPackage)\n\tplugin.RegisterPluginMetadata(hedra.PluginTitle, hedra.PluginVersion, hedra.PluginPackage)\n\tplugin.RegisterPluginMetadata(hume.PluginTitle, hume.PluginVersion, hume.PluginPackage)\n\tplugin.RegisterPluginMetadata(inworld.PluginTitle, inworld.PluginVersion, inworld.PluginPackage)\n\tplugin.RegisterPluginMetadata(keyframe.PluginTitle, keyframe.PluginVersion, keyframe.PluginPackage)\n\tplugin.RegisterPluginMetadata(krisp.PluginTitle, krisp.PluginVersion, krisp.PluginPackage)\n\tplugin.RegisterPluginMetadata(langchain.PluginTitle, langchain.PluginVersion, langchain.PluginPackage)\n\tplugin.RegisterPluginMetadata(lemonslice.PluginTitle, lemonslice.PluginVersion, lemonslice.PluginPackage)\n\tplugin.RegisterPluginMetadata(liveavatar.PluginTitle, liveavatar.PluginVersion, liveavatar.PluginPackage)\n\tplugin.RegisterPluginMetadata(lmnt.PluginTitle, lmnt.PluginVersion, lmnt.PluginPackage)\n\tplugin.RegisterPluginMetadata(minimax.PluginTitle, minimax.PluginVersion, minimax.PluginPackage)\n\tplugin.RegisterPluginMetadata(mistralai.PluginTitle, mistralai.PluginVersion, mistralai.PluginPackage)\n\tplugin.RegisterPluginMetadata(murf.PluginTitle, murf.PluginVersion, murf.PluginPackage)\n\tplugin.RegisterPluginMetadata(neuphonic.PluginTitle, neuphonic.PluginVersion, neuphonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(nltk.PluginTitle, nltk.PluginVersion, nltk.PluginPackage, nltk.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(nvidia.PluginTitle, nvidia.PluginVersion, nvidia.PluginPackage)\n\tplugin.RegisterPluginMetadata(openai.PluginTitle, openai.PluginVersion, openai.PluginPackage)\n\tplugin.RegisterPluginMetadata(perplexity.PluginTitle, perplexity.PluginVersion, perplexity.PluginPackage)\n\tplugin.RegisterPluginMetadata(phonic.PluginTitle, phonic.PluginVersion, phonic.PluginPackage)\n\tplugin.RegisterPluginDownloader(pipecat.PluginTitle, pipecat.PluginVersion, pipecat.PluginPackage, pipecat.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(resemble.PluginTitle, resemble.PluginVersion, resemble.PluginPackage)\n\tplugin.RegisterPluginMetadata(respeecher.PluginTitle, respeecher.PluginVersion, respeecher.PluginPackage)\n\tplugin.RegisterPluginMetadata(rime.PluginTitle, rime.PluginVersion, rime.PluginPackage)\n\tplugin.RegisterPluginMetadata(rtzr.PluginTitle, rtzr.PluginVersion, rtzr.PluginPackage)\n\tplugin.RegisterPluginMetadata(runway.PluginTitle, runway.PluginVersion, runway.PluginPackage)\n\tplugin.RegisterPluginMetadata(sarvam.PluginTitle, sarvam.PluginVersion, sarvam.PluginPackage)\n\tplugin.RegisterPluginDownloader(silero.PluginTitle, silero.PluginVersion, silero.PluginPackage, silero.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(simli.PluginTitle, simli.PluginVersion, simli.PluginPackage)\n\tplugin.RegisterPluginMetadata(simplismart.PluginTitle, simplismart.PluginVersion, simplismart.PluginPackage)\n\tplugin.RegisterPluginMetadata(slng.PluginTitle, slng.PluginVersion, slng.PluginPackage)\n\tplugin.RegisterPluginMetadata(smallestai.PluginTitle, smallestai.PluginVersion, smallestai.PluginPackage)\n\tplugin.RegisterPluginMetadata(soniox.PluginTitle, soniox.PluginVersion, soniox.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechify.PluginTitle, speechify.PluginVersion, speechify.PluginPackage)\n\tplugin.RegisterPluginMetadata(speechmatics.PluginTitle, speechmatics.PluginVersion, speechmatics.PluginPackage)\n\tplugin.RegisterPluginMetadata(spitch.PluginTitle, spitch.PluginVersion, spitch.PluginPackage)\n\tplugin.RegisterPluginMetadata(tavus.PluginTitle, tavus.PluginVersion, tavus.PluginPackage)\n\tplugin.RegisterPluginMetadata(telnyx.PluginTitle, telnyx.PluginVersion, telnyx.PluginPackage)\n\tplugin.RegisterPluginDownloader(ten.PluginTitle, ten.PluginVersion, ten.PluginPackage, ten.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(trugen.PluginTitle, trugen.PluginVersion, trugen.PluginPackage)\n\tplugin.RegisterPluginDownloader(adapterlivekit.PluginTitle, adapterlivekit.PluginVersion, adapterlivekit.PluginPackage, adapterlivekit.Plugin{}.DownloadFiles)\n\tplugin.RegisterPluginMetadata(upliftai.PluginTitle, upliftai.PluginVersion, upliftai.PluginPackage)\n\tplugin.RegisterPluginMetadata(xai.PluginTitle, xai.PluginVersion, xai.PluginPackage)\n}\n\nvar (\n\tappInitLoggerProvider = telemetry.InitLoggerProvider\n\tappShutdownLoggerProvider = telemetry.ShutdownLoggerProvider\n\tappNewMCPServerHTTP = llm.NewMCPServerHTTP\n\tappNewPipecatSmartTurn = func() (agent.AudioTurnDetector, error) {\n\t\treturn pipecat.NewLocalSmartTurn()\n\t}\n)\n\nconst (\n\tproviderAnam = \"anam\"\n\tproviderAnthropic = \"anthropic\"\n\tproviderAssemblyAI = \"assemblyai\"\n\tproviderAsyncAI = \"asyncai\"\n\tproviderAvatario = \"avatario\"\n\tproviderAvatarTalk = \"avatartalk\"\n\tproviderAWS = \"aws\"\n\tproviderAzure = \"azure\"\n\tproviderBaseten = \"baseten\"\n\tproviderBey = \"bey\"\n\tproviderBitHuman = \"bithuman\"\n\tproviderCambai = \"cambai\"\n\tproviderCartesia = \"cartesia\"\n\tproviderCavos = \"cavos\"\n\tproviderCerebras = \"cerebras\"\n\tproviderClova = \"clova\"\n\tproviderCometAPI = \"cometapi\"\n\tproviderDeepgram = \"deepgram\"\n\tproviderDeepSeek = \"deepseek\"\n\tproviderDID = \"did\"\n\tproviderElevenLabs = \"elevenlabs\"\n\tproviderFal = \"fal\"\n\tproviderFireworks = \"fireworks\"\n\tproviderFishAudio = \"fishaudio\"\n\tproviderGladia = \"gladia\"\n\tproviderGnani = \"gnani\"\n\tproviderGoogle = \"google\"\n\tproviderGradium = \"gradium\"\n\tproviderGroq = \"groq\"\n\tproviderHedra = \"hedra\"\n\tproviderHume = \"hume\"\n\tproviderInworld = \"inworld\"\n\tproviderKeyframe = \"keyframe\"\n\tproviderLangChain = \"langchain\"\n\tproviderLemonSlice = \"lemonslice\"\n\tproviderLetta = \"letta\"\n\tproviderLiveAvatar = \"liveavatar\"\n\tproviderLMNT = \"lmnt\"\n\tproviderMinimax = \"minimax\"\n\tproviderMistralAI = \"mistralai\"\n\tproviderMurf = \"murf\"\n\tproviderNeuphonic = \"neuphonic\"\n\tproviderNebius = \"nebius\"\n\tproviderNvidia = \"nvidia\"\n\tproviderOctoAI = \"octoai\"\n\tproviderOllama = \"ollama\"\n\tproviderOpenAI = \"openai\"\n\tproviderOpenRouter = \"openrouter\"\n\tproviderOVHCloud = \"ovhcloud\"\n\tproviderPerplexity = \"perplexity\"\n\tproviderPhonic = \"phonic\"\n\tproviderPipecat = \"pipecat\"\n\tproviderResemble = \"resemble\"\n\tproviderRespeecher = \"respeecher\"\n\tproviderRime = \"rime\"\n\tproviderRtzr = \"rtzr\"\n\tproviderRunway = \"runway\"\n\tproviderSambaNova = \"sambanova\"\n\tproviderSarvam = \"sarvam\"\n\tproviderSilero = \"silero\"\n\tproviderSimli = \"simli\"\n\tproviderSimplismart = \"simplismart\"\n\tproviderSLNG = \"slng\"\n\tproviderSmallestAI = \"smallestai\"\n\tproviderSoniox = \"soniox\"\n\tproviderSpeechify = \"speechify\"\n\tproviderSpeechmatics = \"speechmatics\"\n\tproviderSpitch = \"spitch\"\n\tproviderTavus = \"tavus\"\n\tproviderTelnyx = \"telnyx\"\n\tproviderTen = \"ten\"\n\tproviderTogether = \"together\"\n\tproviderTrugen = \"trugen\"\n\tproviderUltravox = \"ultravox\"\n\tproviderUpliftAI = \"upliftai\"\n\tproviderXAI = \"xai\"\n\tproviderLiveKit = \"livekit\"\n\n\tdefaultAgoraGreeting = \"TEN Agent connected. How can I help you today?\"\n)\n\ntype AppConfig struct {\n\tWorkerOptions worker.WorkerOptions\n\tAgora workeragora.Options\n\tAgoraGreeting string\n\tLogger livekitlogger.Logger\n\tMetricsRegistry *telemetry.MetricRegistry\n\tInstructions string\n\n\tTelemetryLogsEndpoint string\n\tTelemetryLogsHeaders map[string]string\n\n\tInitialChatContext map[string]any\n\tAWSRegion string\n\tLLMProvider string\n\tLLMModel string\n\tLLMBaseURL string\n\tLLMModelOptions map[string]any\n\tLLMExtraHeaders map[string]string\n\tLLMExtraBody map[string]any\n\tLLMFallbackProviders []string\n\tLLMParallelToolCalls *bool\n\tLLMResponseFormat map[string]any\n\tSTTProvider string\n\tSTTFallbackProviders []string\n\tSTTModel string\n\tSTTLanguage string\n\tSTTEncoding string\n\tSTTChainID string\n\tSTTDetectLanguage bool\n\tSTTPunctuate *bool\n\tSTTSpokenPunctuation *bool\n\tSTTProfanityFilter *bool\n\tSTTTagAudioEvents *bool\n\tSTTIncludeTimestamps *bool\n\tSTTWordTimestamps *bool\n\tSTTInterimResults *bool\n\tSTTSmartFormat *bool\n\tSTTNoDelay *bool\n\tSTTEndpointingMS *int\n\tSTTDiarization *bool\n\tSTTMultiSpeaker *bool\n\tSTTFillerWords *bool\n\tSTTVADEvents *bool\n\tSTTNumerals *bool\n\tSTTMIPOptOut *bool\n\tSTTKeywords []deepgram.DeepgramKeyword\n\tSTTRedact []string\n\tSTTTags []string\n\tSTTTask string\n\tSTTChunkLevel string\n\tSTTVersion string\n\tSTTTemperature *float64\n\tSTTSkipVAD *bool\n\tSTTVADKwargs map[string]any\n\tSTTTextTimeoutSeconds *float64\n\tSTTTimestampGranularities []string\n\tSTTCodeSwitching *bool\n\tSTTBitDepth *int\n\tSTTEndpointingSeconds *float64\n\tSTTMaxDurationWithoutEndpointingSeconds *float64\n\tSTTRegion string\n\tSTTCustomVocabulary []any\n\tSTTCustomSpelling map[string][]string\n\tSTTTranslationTargetLanguages []string\n\tSTTTranslationSourceLanguages []string\n\tSTTTranslationModel string\n\tSTTOutputLocale string\n\tSTTOperatingPoint string\n\tSTTTranslationMatchOriginalUtterances *bool\n\tSTTTranslationLipsync *bool\n\tSTTTranslationContextAdaptation *bool\n\tSTTTranslationContext string\n\tSTTTranslationInformal *bool\n\tSTTPreProcessingAudioEnhancer *bool\n\tSTTPreProcessingSpeechThreshold *float64\n\tSTTPrompt string\n\tSTTBaseURL string\n\tSTTModelEndpoints []string\n\tSTTStreamingURL string\n\tSTTSampleRate *int\n\tSTTBufferSizeSeconds *float64\n\tSTTAudioChunkDurationMS *int\n\tSTTMinTurnSilence *int\n\tSTTMaxTurnSilence *int\n\tSTTEndOfTurnConfidenceThreshold *float64\n\tSTTFormatTurns *bool\n\tSTTLanguageDetection *bool\n\tSTTContinuousPartials *bool\n\tSTTInterruptionDelay *int\n\tSTTKeytermsPrompt []string\n\tSTTVADThreshold *float64\n\tSTTVADSilenceThresholdSeconds *float64\n\tSTTVADSpeechPadMS *int\n\tSTTSpeakerLabels *bool\n\tSTTMaxSpeakers *int\n\tSTTDomain string\n\tSTTVocabularyName string\n\tSTTSessionID string\n\tSTTVocabularyFilterMethod string\n\tSTTVocabularyFilterName string\n\tSTTEnableChannelIdentification *bool\n\tSTTNumberOfChannels *int\n\tSTTEnablePartialStabilization *bool\n\tSTTPartialResultsStability string\n\tSTTLanguageModelName string\n\tSTTIdentifyLanguage *bool\n\tSTTIdentifyMultipleLanguages *bool\n\tSTTLanguageOptions string\n\tSTTPreferredLanguage string\n\tSTTVocabularyNames string\n\tSTTVocabularyFilterNames string\n\tSTTOrganizationID string\n\tSTTUserID string\n\tSTTVADBucket *int\n\tSTTVADFlush *bool\n\tSTTVoiceProfile *bool\n\tSTTVoiceProfileTopN *int\n\tSTTMinEndOfTurnSilenceWhenConfident *int\n\tSTTMinSpeakers *int\n\tSTTModelOptions map[string]any\n\tSTTPositiveSpeechThreshold *float64\n\tSTTNegativeSpeechThreshold *float64\n\tSTTMinSpeechFrames *int\n\tSTTFirstTurnMinSpeechFrames *int\n\tSTTNegativeFramesCount *int\n\tSTTNegativeFramesWindow *int\n\tSTTStartSpeechVolumeThreshold *float64\n\tSTTInterruptMinSpeechFrames *int\n\tSTTPreSpeechPadFrames *int\n\tSTTNumInitialIgnoredFrames *int\n\tSTTPreferCurrentSpeaker *bool\n\tVADProvider string\n\tVADMinSpeechDuration *float64\n\tVADMinSilenceDuration *float64\n\tVADPrefixPaddingDuration *float64\n\tVADPaddingDuration *float64\n\tVADMaxBufferedSpeech *float64\n\tVADActivationThreshold *float64\n\tVADDeactivationThreshold *float64\n\tVADUpdateInterval *float64\n\tVADSampleRate *int\n\tAvatarProvider string\n\tTurnDetectorProvider string\n\tBackgroundAudioAmbient string\n\tBackgroundAudioThinking string\n\tTTSProvider string\n\tTTSFallbackProviders []string\n\tTTSModel string\n\tTTSVoice string\n\tTTSRefAudio string\n\tTTSVoiceID string\n\tTTSVoiceProvider string\n\tTTSLanguage string\n\tTTSEncoding string\n\tTTSSampleRate *int\n\tTTSSpeed float64\n\tTTSSpeedSet bool\n\tTTSTemperature *float64\n\tTTSTopP *float64\n\tTTSMaxTokens *int\n\tTTSBufferSize *int\n\tTTSEnhanceNamedEntities *bool\n\tTTSEnableSSMLParsing *bool\n\tTTSAPIVersion string\n\tTTSWordTimestamps *bool\n\tTTSVoiceEmbedding []float64\n\tTTSEmotion string\n\tTTSVolume *float64\n\tTTSPronunciationDictID string\n\tTTSMIPOptOut *bool\n\tTTSLatencyMode string\n\tTTSChunkLength *int\n\tTTSInstructions string\n\tTTSResponseFormat string\n\tTTSBaseURL string\n\tTTSWebsocketURL string\n\tTTSTextType string\n\tTTSNumberOfChannels *int\n\tTTSSampleWidth *int\n\tTTSJSONConfig map[string]any\n\tTTSBitRate *int\n\tTTSSpeakingRate *float64\n\tTTSTrailingSilence *float64\n\tTTSInstantMode *bool\n\tTTSPitch *int\n\tTTSTimestampType string\n\tTTSLoudnessNormalization *bool\n\tTTSTextNormalization *bool\n\tTTSDeliveryMode string\n\tTTSTokenizerProvider string\n\tTTSTokenizerLanguage string\n\tTTSTokenizerMinSentenceLen *int\n\tTTSTokenizerStreamContextLen *int\n\tTTSTextReplacements map[string]string\n\tTTSTextTransforms []string\n\tDisableTTSTextTransforms bool\n\tWordTokenizerProvider string\n\tWordTokenizerLanguage string\n\tTTSStreamPacerEnabled bool\n\tTTSStreamPacerMinRemainingAudioMS *int\n\tTTSStreamPacerMaxTextLength *int\n\tTTSTimestampTransportStrategy string\n\tTTSBufferCharThreshold *int\n\tTTSMaxBufferDelayMS *int\n\tTTSContextGenerationID string\n\tTTSContextUtterances []hume.HumeTTSUtterance\n\tTTSRegion string\n\tTTSModelOptions map[string]any\n\tRealtimeProvider string\n\tRealtimeModel string\n\n\tOpenAIAPIKey string\n\tAnamAPIKey string\n\tAnthropicAPIKey string\n\tAvatarioAPIKey string\n\tAvatarTalkAPIKey string\n\tBeyAPIKey string\n\tBitHumanAPIKey string\n\tGoogleAPIKey string\n\tElevenLabsAPIKey string\n\tGroqAPIKey string\n\tCerebrasAPIKey string\n\tClovaSTTSecret string\n\tClovaSTTInvokeURL string\n\tClovaClientID string\n\tClovaClientSecret string\n\tCometAPIKey string\n\tDeepSeekAPIKey string\n\tDIDAPIKey string\n\tDIDAgentID string\n\tFalAPIKey string\n\tFireworksAPIKey string\n\tFishAudioAPIKey string\n\tGladiaAPIKey string\n\tGnaniAPIKey string\n\tGradiumAPIKey string\n\tHedraAPIKey string\n\tHumeAPIKey string\n\tInworldAPIKey string\n\tKeyframeAPIKey string\n\tLangChainAPIKey string\n\tLemonSliceAPIKey string\n\tLettaAPIKey string\n\tLiveAvatarAPIKey string\n\tLMNTAPIKey string\n\tMinimaxAPIKey string\n\tMistralAPIKey string\n\tMurfAPIKey string\n\tNeuphonicAPIKey string\n\tNebiusAPIKey string\n\tNvidiaAPIKey string\n\tOctoAIAPIKey string\n\tOpenRouterAPIKey string\n\tOVHCloudAPIKey string\n\tPerplexityAPIKey string\n\tPhonicAPIKey string\n\tResembleAPIKey string\n\tRespeecherAPIKey string\n\tRimeAPIKey string\n\tRtzrClientID string\n\tRtzrClientSecret string\n\tRtzrAccessToken string\n\tRunwayAPISecret string\n\tRunwayAvatarID string\n\tRunwayPresetID string\n\tRunwayMaxDuration *int\n\tSambaNovaAPIKey string\n\tSarvamAPIKey string\n\tSimliAPIKey string\n\tSimplismartAPIKey string\n\tSmallestAIAPIKey string\n\tSLNGAPIKey string\n\tSonioxAPIKey string\n\tSpeechifyAPIKey string\n\tSpeechmaticsAPIKey string\n\tSpitchAPIKey string\n\tTavusAPIKey string\n\tTelnyxAPIKey string\n\tTogetherAPIKey string\n\tTrugenAPIKey string\n\tUltravoxAPIKey string\n\tUpliftAIAPIKey string\n\tXAIAPIKey string\n\tAnthropicTools []string\n\tAnthropicComputerWidth *int\n\tAnthropicComputerHeight *int\n\tXAITools []string\n\tXAIAllowedXHandles []string\n\tXAIFileSearchVectorStoreIDs []string\n\tXAIFileSearchMaxResults *int\n\n\tGoogleCredentialsFile string\n\n\tLiveKitInferenceAPIKey string\n\tLiveKitInferenceAPISecret string\n\tAppTools []string\n\tMCPStdioServers []MCPStdioServerConfig\n\tMCPHTTPServers []MCPHTTPServerConfig\n\tIVRDetection bool\n\tIVRSilenceDurationSeconds *float64\n\tWorkflowTask string\n\tWorkflowRequireConfirmation bool\n\tWorkflowAddressPersona string\n\tWorkflowAddressExtraInstructions string\n\tWorkflowEmailPersona string\n\tWorkflowEmailExtraInstructions string\n\tWorkflowDtmfNumDigits *int\n\tWorkflowDtmfAskConfirmation *bool\n\tWorkflowDtmfInputTimeoutSeconds *float64\n\tWorkflowDtmfStopEvent string\n\tWorkflowDtmfExtraInstructions string\n\tWorkflowPhoneNumberExtraInstructions string\n\tWorkflowDOBExtraInstructions string\n\tWorkflowDOBIncludeTime bool\n\tWorkflowNameFirstName *bool\n\tWorkflowNameMiddleName *bool\n\tWorkflowNameLastName *bool\n\tWorkflowNameFormat string\n\tWorkflowNameVerifySpelling bool\n\tWorkflowNameExtraInstructions string\n\tWorkflowWarmTransferSipCallTo string\n\tWorkflowWarmTransferSipTrunkID string\n\tWorkflowWarmTransferSipConnection *livekit.SIPOutboundConfig\n\tWorkflowWarmTransferSipNumber string\n\tWorkflowWarmTransferSipHeaders map[string]string\n\tWorkflowWarmTransferDTMF string\n\tWorkflowWarmTransferRingingTimeout *float64\n\tWorkflowWarmTransferHoldAudio string\n\tWorkflowWarmTransferDisableHoldAudio bool\n\tWorkflowWarmTransferPersona string\n\tWorkflowWarmTransferExtraInstructions string\n\tWorkflowTaskGroupTasks []string\n\tEvalJudges []string\n}\n\ntype MCPStdioServerConfig struct {\n\tCommand string `json:\"command\"`\n\tArgs []string `json:\"args\"`\n\tEnv map[string]string `json:\"env\"`\n\tCwd string `json:\"cwd\"`\n}\n\ntype MCPHTTPServerConfig struct {\n\tURL string `json:\"url\"`\n\tTransportType string `json:\"transportType\"`\n\tAllowedTools []string `json:\"allowedTools\"`\n\tHeaders map[string]string `json:\"headers\"`\n}\n\ntype App struct {\n\tServer *worker.AgentServer\n\tAgent *agent.Agent\n\tSession *agent.AgentSession\n\tRealtimeModel llm.RealtimeModel\n\tEvaluator *evals.JudgeGroup\n\tMCPServers []llm.MCPServer\n\tMetricsRegistry *telemetry.MetricRegistry\n\tRoomIO *workerlivekit.RoomIO\n\tRoomOptions workerlivekit.RoomOptions\n\tConfig AppConfig\n\ttelemetryLogs bool\n}\n\ntype EvaluationSummary struct {\n\tResult *evals.EvaluationResult\n\tScore float64\n\tAllPassed bool\n\tAnyPassed bool\n\tMajorityPassed bool\n\tNoneFailed bool\n}\n\nfunc DefaultConfigFromEnv() AppConfig {\n\treturn AppConfig{\n\t\tWorkerOptions: worker.WorkerOptions{\n\t\t\tTransport: worker.NormalizeWorkerTransport(os.Getenv(\"RTP_AGENT_TRANSPORT\")),\n\t\t},\n\t\tAgora: workeragora.Options{\n\t\t\tAppID: strings.TrimSpace(os.Getenv(\"AGORA_APP_ID\")),\n\t\t\tAppCertificate: strings.TrimSpace(os.Getenv(\"AGORA_APP_CERTIFICATE\")),\n\t\t\tChannel: firstTrimmedEnv(\"AGORA_CHANNEL\", \"AGORA_CHANNEL_NAME\"),\n\t\t\tUID: firstTrimmedEnv(\"AGORA_UID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRemoteStreamID: firstTrimmedEnv(\"AGORA_REMOTE_STREAM_ID\", \"AGORA_USER_UID\"),\n\t\t\tToken: strings.TrimSpace(os.Getenv(\"AGORA_TOKEN\")),\n\t\t\tRTMUserID: firstTrimmedEnv(\"AGORA_RTM_USER_ID\", \"AGORA_STREAM_ID\", \"AGORA_BOT_UID\"),\n\t\t\tRTMToken: strings.TrimSpace(os.Getenv(\"AGORA_RTM_TOKEN\")),\n\t\t\tPublishAudio: getenvOptionalBool(\"AGORA_PUBLISH_AUDIO\"),\n\t\t\tSubscribeAudio: getenvOptionalBool(\"AGORA_SUBSCRIBE_AUDIO\"),\n\t\t\tPublishData: getenvOptionalBool(\"AGORA_PUBLISH_DATA\"),\n\t\t\tRTMEnabled: getenvOptionalBool(\"AGORA_RTM_ENABLED\"),\n\t\t},\n\t\tAgoraGreeting: getenvTrimmedDefaultUnsetOnly(\"AGORA_GREETING\", defaultAgoraGreeting),\n\t\tInstructions: getenvDefault(\"RTP_AGENT_INSTRUCTIONS\", \"You are a helpful realtime voice agent.\"),\n\t\tTelemetryLogsEndpoint: os.Getenv(\"RTP_AGENT_OTLP_LOGS_ENDPOINT\"),\n\t\tTelemetryLogsHeaders: splitEnvStringMap(\"RTP_AGENT_OTLP_LOGS_HEADERS\"),\n\t\tInitialChatContext: jsonEnvMap(\"RTP_AGENT_CHAT_CONTEXT_JSON\"),\n\t\tAWSRegion: firstEnv(\"RTP_AGENT_AWS_REGION\", \"AWS_REGION\"),\n\t\tLLMProvider: normalizedEnv(\"RTP_AGENT_LLM_PROVIDER\"),\n\t\tLLMModel: os.Getenv(\"RTP_AGENT_LLM_MODEL\"),\n\t\tLLMBaseURL: os.Getenv(\"RTP_AGENT_LLM_BASE_URL\"),\n\t\tLLMModelOptions: splitEnvMap(\"RTP_AGENT_LLM_MODEL_OPTIONS\"),\n\t\tLLMExtraHeaders: splitEnvStringMap(\"RTP_AGENT_LLM_EXTRA_HEADERS\"),\n\t\tLLMExtraBody: splitEnvMap(\"RTP_AGENT_LLM_JSON_CONFIG\"),\n\t\tLLMFallbackProviders: splitEnvList(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS\"),\n\t\tLLMParallelToolCalls: getenvOptionalBool(\"RTP_AGENT_LLM_PARALLEL_TOOL_CALLS\"),\n\t\tLLMResponseFormat: splitEnvMap(\"RTP_AGENT_LLM_RESPONSE_FORMAT\"),\n\t\tSTTProvider: normalizedEnv(\"RTP_AGENT_STT_PROVIDER\"),\n\t\tSTTFallbackProviders: splitEnvList(\"RTP_AGENT_STT_FALLBACK_PROVIDERS\"),\n\t\tSTTModel: os.Getenv(\"RTP_AGENT_STT_MODEL\"),\n\t\tSTTLanguage: os.Getenv(\"RTP_AGENT_STT_LANGUAGE\"),\n\t\tSTTEncoding: os.Getenv(\"RTP_AGENT_STT_ENCODING\"),\n\t\tSTTChainID: os.Getenv(\"RTP_AGENT_STT_CHAIN_ID\"),\n\t\tSTTDetectLanguage: getenvBool(\"RTP_AGENT_STT_DETECT_LANGUAGE\"),\n\t\tSTTPunctuate: getenvOptionalBool(\"RTP_AGENT_STT_PUNCTUATE\"),\n\t\tSTTSpokenPunctuation: getenvOptionalBool(\"RTP_AGENT_STT_SPOKEN_PUNCTUATION\"),\n\t\tSTTProfanityFilter: getenvOptionalBool(\"RTP_AGENT_STT_PROFANITY_FILTER\"),\n\t\tSTTTagAudioEvents: getenvOptionalBool(\"RTP_AGENT_STT_TAG_AUDIO_EVENTS\"),\n\t\tSTTIncludeTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_INCLUDE_TIMESTAMPS\"),\n\t\tSTTWordTimestamps: getenvOptionalBool(\"RTP_AGENT_STT_WORD_TIMESTAMPS\"),\n\t\tSTTInterimResults: getenvOptionalBool(\"RTP_AGENT_STT_INTERIM_RESULTS\"),\n\t\tSTTSmartFormat: getenvOptionalBool(\"RTP_AGENT_STT_SMART_FORMAT\"),\n\t\tSTTNoDelay: getenvOptionalBool(\"RTP_AGENT_STT_NO_DELAY\"),\n\t\tSTTEndpointingMS: getenvOptionalInt(\"RTP_AGENT_STT_ENDPOINTING_MS\"),\n\t\tSTTDiarization: getenvOptionalBool(\"RTP_AGENT_STT_DIARIZATION\"),\n\t\tSTTMultiSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_MULTI_SPEAKER\"),\n\t\tSTTFillerWords: getenvOptionalBool(\"RTP_AGENT_STT_FILLER_WORDS\"),\n\t\tSTTVADEvents: getenvOptionalBool(\"RTP_AGENT_STT_VAD_EVENTS\"),\n\t\tSTTNumerals: getenvOptionalBool(\"RTP_AGENT_STT_NUMERALS\"),\n\t\tSTTMIPOptOut: getenvOptionalBool(\"RTP_AGENT_STT_MIP_OPT_OUT\"),\n\t\tSTTKeywords: splitEnvDeepgramKeywords(\"RTP_AGENT_STT_KEYWORDS\"),\n\t\tSTTRedact: splitEnvList(\"RTP_AGENT_STT_REDACT\"),\n\t\tSTTTags: splitEnvList(\"RTP_AGENT_STT_TAGS\"),\n\t\tSTTTask: os.Getenv(\"RTP_AGENT_STT_TASK\"),\n\t\tSTTChunkLevel: os.Getenv(\"RTP_AGENT_STT_CHUNK_LEVEL\"),\n\t\tSTTVersion: os.Getenv(\"RTP_AGENT_STT_VERSION\"),\n\t\tSTTTemperature: getenvOptionalFloat(\"RTP_AGENT_STT_TEMPERATURE\"),\n\t\tSTTSkipVAD: getenvOptionalBool(\"RTP_AGENT_STT_SKIP_VAD\"),\n\t\tSTTVADKwargs: splitEnvMap(\"RTP_AGENT_STT_VAD_KWARGS\"),\n\t\tSTTTextTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_TEXT_TIMEOUT_SECONDS\"),\n\t\tSTTTimestampGranularities: splitEnvList(\"RTP_AGENT_STT_TIMESTAMP_GRANULARITIES\"),\n\t\tSTTCodeSwitching: getenvOptionalBool(\"RTP_AGENT_STT_CODE_SWITCHING\"),\n\t\tSTTBitDepth: getenvOptionalInt(\"RTP_AGENT_STT_BIT_DEPTH\"),\n\t\tSTTEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_ENDPOINTING_SECONDS\"),\n\t\tSTTMaxDurationWithoutEndpointingSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_MAX_DURATION_WITHOUT_ENDPOINTING_SECONDS\"),\n\t\tSTTRegion: os.Getenv(\"RTP_AGENT_STT_REGION\"),\n\t\tSTTCustomVocabulary: splitEnvAnyList(\"RTP_AGENT_STT_CUSTOM_VOCABULARY\"),\n\t\tSTTCustomSpelling: splitEnvStringSliceMap(\"RTP_AGENT_STT_CUSTOM_SPELLING\"),\n\t\tSTTTranslationTargetLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_TARGET_LANGUAGES\"),\n\t\tSTTTranslationSourceLanguages: splitEnvList(\"RTP_AGENT_STT_TRANSLATION_SOURCE_LANGUAGES\"),\n\t\tSTTTranslationModel: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_MODEL\"),\n\t\tSTTOutputLocale: os.Getenv(\"RTP_AGENT_STT_OUTPUT_LOCALE\"),\n\t\tSTTOperatingPoint: os.Getenv(\"RTP_AGENT_STT_OPERATING_POINT\"),\n\t\tSTTTranslationMatchOriginalUtterances: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_MATCH_ORIGINAL_UTTERANCES\"),\n\t\tSTTTranslationLipsync: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_LIPSYNC\"),\n\t\tSTTTranslationContextAdaptation: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_CONTEXT_ADAPTATION\"),\n\t\tSTTTranslationContext: os.Getenv(\"RTP_AGENT_STT_TRANSLATION_CONTEXT\"),\n\t\tSTTTranslationInformal: getenvOptionalBool(\"RTP_AGENT_STT_TRANSLATION_INFORMAL\"),\n\t\tSTTPreProcessingAudioEnhancer: getenvOptionalBool(\"RTP_AGENT_STT_PRE_PROCESSING_AUDIO_ENHANCER\"),\n\t\tSTTPreProcessingSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_PRE_PROCESSING_SPEECH_THRESHOLD\"),\n\t\tSTTPrompt: os.Getenv(\"RTP_AGENT_STT_PROMPT\"),\n\t\tSTTBaseURL: os.Getenv(\"RTP_AGENT_STT_BASE_URL\"),\n\t\tSTTModelEndpoints: splitEnvList(\"RTP_AGENT_STT_MODEL_ENDPOINTS\"),\n\t\tSTTStreamingURL: os.Getenv(\"RTP_AGENT_STT_STREAMING_URL\"),\n\t\tSTTSampleRate: getenvOptionalInt(\"RTP_AGENT_STT_SAMPLE_RATE\"),\n\t\tSTTBufferSizeSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_BUFFER_SIZE_SECONDS\"),\n\t\tSTTAudioChunkDurationMS: getenvOptionalInt(\"RTP_AGENT_STT_AUDIO_CHUNK_DURATION_MS\"),\n\t\tSTTMinTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MIN_TURN_SILENCE\"),\n\t\tSTTMaxTurnSilence: getenvOptionalInt(\"RTP_AGENT_STT_MAX_TURN_SILENCE\"),\n\t\tSTTEndOfTurnConfidenceThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_END_OF_TURN_CONFIDENCE_THRESHOLD\"),\n\t\tSTTFormatTurns: getenvOptionalBool(\"RTP_AGENT_STT_FORMAT_TURNS\"),\n\t\tSTTLanguageDetection: getenvOptionalBool(\"RTP_AGENT_STT_LANGUAGE_DETECTION\"),\n\t\tSTTContinuousPartials: getenvOptionalBool(\"RTP_AGENT_STT_CONTINUOUS_PARTIALS\"),\n\t\tSTTInterruptionDelay: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPTION_DELAY\"),\n\t\tSTTKeytermsPrompt: splitEnvList(\"RTP_AGENT_STT_KEYTERMS_PROMPT\"),\n\t\tSTTVADThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_THRESHOLD\"),\n\t\tSTTVADSilenceThresholdSeconds: getenvOptionalFloat(\"RTP_AGENT_STT_VAD_SILENCE_THRESHOLD_SECONDS\"),\n\t\tSTTVADSpeechPadMS: getenvOptionalInt(\"RTP_AGENT_STT_VAD_SPEECH_PAD_MS\"),\n\t\tSTTSpeakerLabels: getenvOptionalBool(\"RTP_AGENT_STT_SPEAKER_LABELS\"),\n\t\tSTTMaxSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MAX_SPEAKERS\"),\n\t\tSTTDomain: os.Getenv(\"RTP_AGENT_STT_DOMAIN\"),\n\t\tSTTVocabularyName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAME\"),\n\t\tSTTSessionID: os.Getenv(\"RTP_AGENT_STT_SESSION_ID\"),\n\t\tSTTVocabularyFilterMethod: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_METHOD\"),\n\t\tSTTVocabularyFilterName: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAME\"),\n\t\tSTTEnableChannelIdentification: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_CHANNEL_IDENTIFICATION\"),\n\t\tSTTNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_STT_NUMBER_OF_CHANNELS\"),\n\t\tSTTEnablePartialStabilization: getenvOptionalBool(\"RTP_AGENT_STT_ENABLE_PARTIAL_RESULTS_STABILIZATION\"),\n\t\tSTTPartialResultsStability: os.Getenv(\"RTP_AGENT_STT_PARTIAL_RESULTS_STABILITY\"),\n\t\tSTTLanguageModelName: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_MODEL_NAME\"),\n\t\tSTTIdentifyLanguage: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_LANGUAGE\"),\n\t\tSTTIdentifyMultipleLanguages: getenvOptionalBool(\"RTP_AGENT_STT_IDENTIFY_MULTIPLE_LANGUAGES\"),\n\t\tSTTLanguageOptions: os.Getenv(\"RTP_AGENT_STT_LANGUAGE_OPTIONS\"),\n\t\tSTTPreferredLanguage: os.Getenv(\"RTP_AGENT_STT_PREFERRED_LANGUAGE\"),\n\t\tSTTVocabularyNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_NAMES\"),\n\t\tSTTVocabularyFilterNames: os.Getenv(\"RTP_AGENT_STT_VOCABULARY_FILTER_NAMES\"),\n\t\tSTTOrganizationID: os.Getenv(\"RTP_AGENT_STT_ORGANIZATION_ID\"),\n\t\tSTTUserID: os.Getenv(\"RTP_AGENT_STT_USER_ID\"),\n\t\tSTTVADBucket: getenvOptionalInt(\"RTP_AGENT_STT_VAD_BUCKET\"),\n\t\tSTTVADFlush: getenvOptionalBool(\"RTP_AGENT_STT_VAD_FLUSH\"),\n\t\tSTTVoiceProfile: getenvOptionalBool(\"RTP_AGENT_STT_VOICE_PROFILE\"),\n\t\tSTTVoiceProfileTopN: getenvOptionalInt(\"RTP_AGENT_STT_VOICE_PROFILE_TOP_N\"),\n\t\tSTTMinEndOfTurnSilenceWhenConfident: getenvOptionalInt(\"RTP_AGENT_STT_MIN_END_OF_TURN_SILENCE_WHEN_CONFIDENT\"),\n\t\tSTTMinSpeakers: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEAKERS\"),\n\t\tSTTModelOptions: splitEnvMap(\"RTP_AGENT_STT_MODEL_OPTIONS\"),\n\t\tSTTPositiveSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_POSITIVE_SPEECH_THRESHOLD\"),\n\t\tSTTNegativeSpeechThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_NEGATIVE_SPEECH_THRESHOLD\"),\n\t\tSTTMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_MIN_SPEECH_FRAMES\"),\n\t\tSTTFirstTurnMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_FIRST_TURN_MIN_SPEECH_FRAMES\"),\n\t\tSTTNegativeFramesCount: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_COUNT\"),\n\t\tSTTNegativeFramesWindow: getenvOptionalInt(\"RTP_AGENT_STT_NEGATIVE_FRAMES_WINDOW\"),\n\t\tSTTStartSpeechVolumeThreshold: getenvOptionalFloat(\"RTP_AGENT_STT_START_SPEECH_VOLUME_THRESHOLD\"),\n\t\tSTTInterruptMinSpeechFrames: getenvOptionalInt(\"RTP_AGENT_STT_INTERRUPT_MIN_SPEECH_FRAMES\"),\n\t\tSTTPreSpeechPadFrames: getenvOptionalInt(\"RTP_AGENT_STT_PRE_SPEECH_PAD_FRAMES\"),\n\t\tSTTNumInitialIgnoredFrames: getenvOptionalInt(\"RTP_AGENT_STT_NUM_INITIAL_IGNORED_FRAMES\"),\n\t\tSTTPreferCurrentSpeaker: getenvOptionalBool(\"RTP_AGENT_STT_PREFER_CURRENT_SPEAKER\"),\n\t\tVADProvider: normalizedEnv(\"RTP_AGENT_VAD_PROVIDER\"),\n\t\tVADMinSpeechDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SPEECH_DURATION\"),\n\t\tVADMinSilenceDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_MIN_SILENCE_DURATION\"),\n\t\tVADPrefixPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PREFIX_PADDING_DURATION\"),\n\t\tVADPaddingDuration: getenvOptionalFloat(\"RTP_AGENT_VAD_PADDING_DURATION\"),\n\t\tVADMaxBufferedSpeech: getenvOptionalFloat(\"RTP_AGENT_VAD_MAX_BUFFERED_SPEECH\"),\n\t\tVADActivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_ACTIVATION_THRESHOLD\"),\n\t\tVADDeactivationThreshold: getenvOptionalFloat(\"RTP_AGENT_VAD_DEACTIVATION_THRESHOLD\"),\n\t\tVADUpdateInterval: getenvOptionalFloat(\"RTP_AGENT_VAD_UPDATE_INTERVAL\"),\n\t\tVADSampleRate: getenvOptionalInt(\"RTP_AGENT_VAD_SAMPLE_RATE\"),\n\t\tAvatarProvider: normalizedEnv(\"RTP_AGENT_AVATAR_PROVIDER\"),\n\t\tTurnDetectorProvider: normalizedEnv(\"RTP_AGENT_TURN_DETECTOR_PROVIDER\"),\n\t\tBackgroundAudioAmbient: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_AMBIENT\"),\n\t\tBackgroundAudioThinking: os.Getenv(\"RTP_AGENT_BACKGROUND_AUDIO_THINKING\"),\n\t\tTTSProvider: normalizedEnv(\"RTP_AGENT_TTS_PROVIDER\"),\n\t\tTTSFallbackProviders: splitEnvList(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS\"),\n\t\tTTSModel: os.Getenv(\"RTP_AGENT_TTS_MODEL\"),\n\t\tTTSVoice: os.Getenv(\"RTP_AGENT_TTS_VOICE\"),\n\t\tTTSRefAudio: os.Getenv(\"RTP_AGENT_TTS_REF_AUDIO\"),\n\t\tTTSVoiceID: os.Getenv(\"RTP_AGENT_TTS_VOICE_ID\"),\n\t\tTTSVoiceProvider: os.Getenv(\"RTP_AGENT_TTS_VOICE_PROVIDER\"),\n\t\tTTSLanguage: os.Getenv(\"RTP_AGENT_TTS_LANGUAGE\"),\n\t\tTTSEncoding: os.Getenv(\"RTP_AGENT_TTS_ENCODING\"),\n\t\tTTSSampleRate: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_RATE\"),\n\t\tTTSSpeed: getenvFloat(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSSpeedSet: envIsSet(\"RTP_AGENT_TTS_SPEED\"),\n\t\tTTSTemperature: getenvOptionalFloat(\"RTP_AGENT_TTS_TEMPERATURE\"),\n\t\tTTSTopP: getenvOptionalFloat(\"RTP_AGENT_TTS_TOP_P\"),\n\t\tTTSMaxTokens: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_TOKENS\"),\n\t\tTTSBufferSize: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_SIZE\"),\n\t\tTTSEnhanceNamedEntities: getenvOptionalBool(\"RTP_AGENT_TTS_ENHANCE_NAMED_ENTITIES\"),\n\t\tTTSEnableSSMLParsing: getenvOptionalBool(\"RTP_AGENT_TTS_ENABLE_SSML_PARSING\"),\n\t\tTTSAPIVersion: os.Getenv(\"RTP_AGENT_TTS_API_VERSION\"),\n\t\tTTSWordTimestamps: getenvOptionalBool(\"RTP_AGENT_TTS_WORD_TIMESTAMPS\"),\n\t\tTTSVoiceEmbedding: splitEnvFloatList(\"RTP_AGENT_TTS_VOICE_EMBEDDING\"),\n\t\tTTSEmotion: os.Getenv(\"RTP_AGENT_TTS_EMOTION\"),\n\t\tTTSVolume: getenvOptionalFloat(\"RTP_AGENT_TTS_VOLUME\"),\n\t\tTTSPronunciationDictID: os.Getenv(\"RTP_AGENT_TTS_PRONUNCIATION_DICT_ID\"),\n\t\tTTSMIPOptOut: getenvOptionalBool(\"RTP_AGENT_TTS_MIP_OPT_OUT\"),\n\t\tTTSLatencyMode: os.Getenv(\"RTP_AGENT_TTS_LATENCY_MODE\"),\n\t\tTTSChunkLength: getenvOptionalInt(\"RTP_AGENT_TTS_CHUNK_LENGTH\"),\n\t\tTTSInstructions: os.Getenv(\"RTP_AGENT_TTS_INSTRUCTIONS\"),\n\t\tTTSResponseFormat: os.Getenv(\"RTP_AGENT_TTS_RESPONSE_FORMAT\"),\n\t\tTTSBaseURL: os.Getenv(\"RTP_AGENT_TTS_BASE_URL\"),\n\t\tTTSWebsocketURL: os.Getenv(\"RTP_AGENT_TTS_WEBSOCKET_URL\"),\n\t\tTTSTextType: os.Getenv(\"RTP_AGENT_TTS_TEXT_TYPE\"),\n\t\tTTSNumberOfChannels: getenvOptionalInt(\"RTP_AGENT_TTS_NUMBER_OF_CHANNELS\"),\n\t\tTTSSampleWidth: getenvOptionalInt(\"RTP_AGENT_TTS_SAMPLE_WIDTH\"),\n\t\tTTSJSONConfig: splitEnvMap(\"RTP_AGENT_TTS_JSON_CONFIG\"),\n\t\tTTSBitRate: getenvOptionalInt(\"RTP_AGENT_TTS_BIT_RATE\"),\n\t\tTTSSpeakingRate: getenvOptionalFloat(\"RTP_AGENT_TTS_SPEAKING_RATE\"),\n\t\tTTSTrailingSilence: getenvOptionalFloat(\"RTP_AGENT_TTS_TRAILING_SILENCE\"),\n\t\tTTSInstantMode: getenvOptionalBool(\"RTP_AGENT_TTS_INSTANT_MODE\"),\n\t\tTTSPitch: getenvOptionalInt(\"RTP_AGENT_TTS_PITCH\"),\n\t\tTTSTimestampType: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TYPE\"),\n\t\tTTSLoudnessNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_LOUDNESS_NORMALIZATION\"),\n\t\tTTSTextNormalization: getenvOptionalBool(\"RTP_AGENT_TTS_TEXT_NORMALIZATION\"),\n\t\tTTSDeliveryMode: os.Getenv(\"RTP_AGENT_TTS_DELIVERY_MODE\"),\n\t\tTTSTokenizerProvider: normalizedEnv(\"RTP_AGENT_TTS_TOKENIZER_PROVIDER\"),\n\t\tTTSTokenizerLanguage: os.Getenv(\"RTP_AGENT_TTS_TOKENIZER_LANGUAGE\"),\n\t\tTTSTokenizerMinSentenceLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_MIN_SENTENCE_LEN\"),\n\t\tTTSTokenizerStreamContextLen: getenvOptionalInt(\"RTP_AGENT_TTS_TOKENIZER_STREAM_CONTEXT_LEN\"),\n\t\tTTSTextReplacements: splitEnvStringMap(\"RTP_AGENT_TTS_TEXT_REPLACEMENTS\"),\n\t\tTTSTextTransforms: splitEnvList(\"RTP_AGENT_TTS_TEXT_TRANSFORMS\"),\n\t\tDisableTTSTextTransforms: getenvBool(\"RTP_AGENT_DISABLE_TTS_TEXT_TRANSFORMS\"),\n\t\tWordTokenizerProvider: normalizedEnv(\"RTP_AGENT_WORD_TOKENIZER_PROVIDER\"),\n\t\tWordTokenizerLanguage: os.Getenv(\"RTP_AGENT_WORD_TOKENIZER_LANGUAGE\"),\n\t\tTTSStreamPacerEnabled: getenvBool(\"RTP_AGENT_TTS_STREAM_PACER_ENABLED\"),\n\t\tTTSStreamPacerMinRemainingAudioMS: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MIN_REMAINING_AUDIO_MS\"),\n\t\tTTSStreamPacerMaxTextLength: getenvOptionalInt(\"RTP_AGENT_TTS_STREAM_PACER_MAX_TEXT_LENGTH\"),\n\t\tTTSTimestampTransportStrategy: os.Getenv(\"RTP_AGENT_TTS_TIMESTAMP_TRANSPORT_STRATEGY\"),\n\t\tTTSBufferCharThreshold: getenvOptionalInt(\"RTP_AGENT_TTS_BUFFER_CHAR_THRESHOLD\"),\n\t\tTTSMaxBufferDelayMS: getenvOptionalInt(\"RTP_AGENT_TTS_MAX_BUFFER_DELAY_MS\"),\n\t\tTTSContextGenerationID: os.Getenv(\"RTP_AGENT_TTS_CONTEXT_GENERATION_ID\"),\n\t\tTTSContextUtterances: splitEnvHumeTTSUtterances(\"RTP_AGENT_TTS_CONTEXT_UTTERANCES\"),\n\t\tTTSRegion: os.Getenv(\"RTP_AGENT_TTS_REGION\"),\n\t\tTTSModelOptions: splitEnvMap(\"RTP_AGENT_TTS_MODEL_OPTIONS\"),\n\t\tRealtimeProvider: normalizedEnv(\"RTP_AGENT_REALTIME_PROVIDER\"),\n\t\tRealtimeModel: os.Getenv(\"RTP_AGENT_REALTIME_MODEL\"),\n\t\tOpenAIAPIKey: os.Getenv(\"OPENAI_API_KEY\"),\n\t\tAnamAPIKey: os.Getenv(\"ANAM_API_KEY\"),\n\t\tAnthropicAPIKey: os.Getenv(\"ANTHROPIC_API_KEY\"),\n\t\tAvatarioAPIKey: os.Getenv(\"AVATARIO_API_KEY\"),\n\t\tAvatarTalkAPIKey: os.Getenv(\"AVATARTALK_API_KEY\"),\n\t\tBeyAPIKey: os.Getenv(\"BEY_API_KEY\"),\n\t\tBitHumanAPIKey: os.Getenv(\"BITHUMAN_API_KEY\"),\n\t\tGoogleAPIKey: os.Getenv(\"GOOGLE_API_KEY\"),\n\t\tElevenLabsAPIKey: firstEnv(\"ELEVENLABS_API_KEY\", \"ELEVEN_API_KEY\"),\n\t\tGroqAPIKey: os.Getenv(\"GROQ_API_KEY\"),\n\t\tCerebrasAPIKey: os.Getenv(\"CEREBRAS_API_KEY\"),\n\t\tClovaSTTSecret: os.Getenv(\"CLOVA_STT_SECRET\"),\n\t\tClovaSTTInvokeURL: os.Getenv(\"CLOVA_STT_INVOKE_URL\"),\n\t\tClovaClientID: os.Getenv(\"CLOVA_CLIENT_ID\"),\n\t\tClovaClientSecret: os.Getenv(\"CLOVA_CLIENT_SECRET\"),\n\t\tCometAPIKey: os.Getenv(\"COMETAPI_API_KEY\"),\n\t\tDeepSeekAPIKey: os.Getenv(\"DEEPSEEK_API_KEY\"),\n\t\tDIDAPIKey: os.Getenv(\"DID_API_KEY\"),\n\t\tDIDAgentID: os.Getenv(\"DID_AGENT_ID\"),\n\t\tFalAPIKey: firstEnv(\"FAL_KEY\", \"FAL_API_KEY\"),\n\t\tFireworksAPIKey: os.Getenv(\"FIREWORKS_API_KEY\"),\n\t\tFishAudioAPIKey: firstEnv(\"FISHAUDIO_API_KEY\", \"FISH_AUDIO_API_KEY\"),\n\t\tGladiaAPIKey: os.Getenv(\"GLADIA_API_KEY\"),\n\t\tGnaniAPIKey: os.Getenv(\"GNANI_API_KEY\"),\n\t\tGradiumAPIKey: os.Getenv(\"GRADIUM_API_KEY\"),\n\t\tHedraAPIKey: os.Getenv(\"HEDRA_API_KEY\"),\n\t\tHumeAPIKey: os.Getenv(\"HUME_API_KEY\"),\n\t\tInworldAPIKey: os.Getenv(\"INWORLD_API_KEY\"),\n\t\tKeyframeAPIKey: os.Getenv(\"KEYFRAME_API_KEY\"),\n\t\tLangChainAPIKey: os.Getenv(\"LANGCHAIN_API_KEY\"),\n\t\tLemonSliceAPIKey: os.Getenv(\"LEMONSLICE_API_KEY\"),\n\t\tLettaAPIKey: os.Getenv(\"LETTA_API_KEY\"),\n\t\tLiveAvatarAPIKey: os.Getenv(\"LIVEAVATAR_API_KEY\"),\n\t\tLMNTAPIKey: os.Getenv(\"LMNT_API_KEY\"),\n\t\tMinimaxAPIKey: os.Getenv(\"MINIMAX_API_KEY\"),\n\t\tMistralAPIKey: os.Getenv(\"MISTRAL_API_KEY\"),\n\t\tMurfAPIKey: os.Getenv(\"MURF_API_KEY\"),\n\t\tNeuphonicAPIKey: os.Getenv(\"NEUPHONIC_API_KEY\"),\n\t\tNebiusAPIKey: os.Getenv(\"NEBIUS_API_KEY\"),\n\t\tNvidiaAPIKey: os.Getenv(\"NVIDIA_API_KEY\"),\n\t\tOctoAIAPIKey: os.Getenv(\"OCTOAI_TOKEN\"),\n\t\tOpenRouterAPIKey: os.Getenv(\"OPENROUTER_API_KEY\"),\n\t\tOVHCloudAPIKey: os.Getenv(\"OVHCLOUD_API_KEY\"),\n\t\tPerplexityAPIKey: os.Getenv(\"PERPLEXITY_API_KEY\"),\n\t\tPhonicAPIKey: os.Getenv(\"PHONIC_API_KEY\"),\n\t\tResembleAPIKey: os.Getenv(\"RESEMBLE_API_KEY\"),\n\t\tRespeecherAPIKey: os.Getenv(\"RESPEECHER_API_KEY\"),\n\t\tRimeAPIKey: os.Getenv(\"RIME_API_KEY\"),\n\t\tRtzrClientID: os.Getenv(\"RTZR_CLIENT_ID\"),\n\t\tRtzrClientSecret: os.Getenv(\"RTZR_CLIENT_SECRET\"),\n\t\tRtzrAccessToken: os.Getenv(\"RTZR_ACCESS_TOKEN\"),\n\t\tRunwayAPISecret: os.Getenv(\"RUNWAYML_API_SECRET\"),\n\t\tRunwayAvatarID: os.Getenv(\"RTP_AGENT_RUNWAY_AVATAR_ID\"),\n\t\tRunwayPresetID: os.Getenv(\"RTP_AGENT_RUNWAY_PRESET_ID\"),\n\t\tRunwayMaxDuration: getenvOptionalInt(\"RTP_AGENT_RUNWAY_MAX_DURATION\"),\n\t\tSambaNovaAPIKey: os.Getenv(\"SAMBANOVA_API_KEY\"),\n\t\tSarvamAPIKey: os.Getenv(\"SARVAM_API_KEY\"),\n\t\tSimliAPIKey: os.Getenv(\"SIMLI_API_KEY\"),\n\t\tSimplismartAPIKey: os.Getenv(\"SIMPLISMART_API_KEY\"),\n\t\tSmallestAIAPIKey: os.Getenv(\"SMALLESTAI_API_KEY\"),\n\t\tSLNGAPIKey: os.Getenv(\"SLNG_API_KEY\"),\n\t\tSonioxAPIKey: os.Getenv(\"SONIOX_API_KEY\"),\n\t\tSpeechifyAPIKey: os.Getenv(\"SPEECHIFY_API_KEY\"),\n\t\tSpeechmaticsAPIKey: os.Getenv(\"SPEECHMATICS_API_KEY\"),\n\t\tSpitchAPIKey: os.Getenv(\"SPITCH_API_KEY\"),\n\t\tTavusAPIKey: os.Getenv(\"TAVUS_API_KEY\"),\n\t\tTelnyxAPIKey: os.Getenv(\"TELNYX_API_KEY\"),\n\t\tTogetherAPIKey: os.Getenv(\"TOGETHER_API_KEY\"),\n\t\tTrugenAPIKey: os.Getenv(\"TRUGEN_API_KEY\"),\n\t\tUltravoxAPIKey: os.Getenv(\"ULTRAVOX_API_KEY\"),\n\t\tUpliftAIAPIKey: os.Getenv(\"UPLIFTAI_API_KEY\"),\n\t\tXAIAPIKey: os.Getenv(\"XAI_API_KEY\"),\n\t\tAnthropicTools: splitEnvList(\"RTP_AGENT_ANTHROPIC_TOOLS\"),\n\t\tAnthropicComputerWidth: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_WIDTH\"),\n\t\tAnthropicComputerHeight: getenvOptionalInt(\"RTP_AGENT_ANTHROPIC_COMPUTER_HEIGHT\"),\n\t\tXAITools: splitEnvList(\"RTP_AGENT_XAI_TOOLS\"),\n\t\tXAIAllowedXHandles: splitEnvList(\"RTP_AGENT_XAI_ALLOWED_X_HANDLES\"),\n\t\tXAIFileSearchVectorStoreIDs: splitEnvList(\"RTP_AGENT_XAI_FILE_SEARCH_VECTOR_STORE_IDS\"),\n\t\tXAIFileSearchMaxResults: getenvOptionalInt(\"RTP_AGENT_XAI_FILE_SEARCH_MAX_RESULTS\"),\n\t\tGoogleCredentialsFile: firstEnv(\"RTP_AGENT_GOOGLE_CREDENTIALS_FILE\", \"GOOGLE_APPLICATION_CREDENTIALS\"),\n\t\tLiveKitInferenceAPIKey: os.Getenv(\"LIVEKIT_API_KEY\"),\n\t\tLiveKitInferenceAPISecret: os.Getenv(\"LIVEKIT_API_SECRET\"),\n\t\tAppTools: splitEnvList(\"RTP_AGENT_TOOLS\"),\n\t\tMCPStdioServers: mcpStdioServersFromEnv(\"RTP_AGENT_MCP_STDIO_SERVERS\"),\n\t\tMCPHTTPServers: mcpHTTPServersFromEnv(\"RTP_AGENT_MCP_HTTP_SERVERS\"),\n\t\tIVRDetection: getenvBool(\"RTP_AGENT_IVR_DETECTION\"),\n\t\tIVRSilenceDurationSeconds: getenvOptionalFloat(\"RTP_AGENT_IVR_SILENCE_DURATION_SECONDS\"),\n\t\tWorkflowTask: normalizedEnv(\"RTP_AGENT_WORKFLOW_TASK\"),\n\t\tWorkflowRequireConfirmation: getenvBool(\"RTP_AGENT_WORKFLOW_REQUIRE_CONFIRMATION\"),\n\t\tWorkflowAddressPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_PERSONA\"),\n\t\tWorkflowAddressExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_ADDRESS_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowEmailPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_PERSONA\"),\n\t\tWorkflowEmailExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_EMAIL_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDtmfNumDigits: getenvOptionalInt(\"RTP_AGENT_WORKFLOW_DTMF_NUM_DIGITS\"),\n\t\tWorkflowDtmfAskConfirmation: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_DTMF_ASK_CONFIRMATION\"),\n\t\tWorkflowDtmfInputTimeoutSeconds: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_DTMF_INPUT_TIMEOUT_SECONDS\"),\n\t\tWorkflowDtmfStopEvent: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_STOP_EVENT\"),\n\t\tWorkflowDtmfExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DTMF_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowPhoneNumberExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_PHONE_NUMBER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_DOB_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowDOBIncludeTime: getenvBool(\"RTP_AGENT_WORKFLOW_DOB_INCLUDE_TIME\"),\n\t\tWorkflowNameFirstName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_FIRST_NAME\"),\n\t\tWorkflowNameMiddleName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_MIDDLE_NAME\"),\n\t\tWorkflowNameLastName: getenvOptionalBool(\"RTP_AGENT_WORKFLOW_NAME_LAST_NAME\"),\n\t\tWorkflowNameFormat: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_FORMAT\"),\n\t\tWorkflowNameVerifySpelling: getenvBool(\"RTP_AGENT_WORKFLOW_NAME_VERIFY_SPELLING\"),\n\t\tWorkflowNameExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_NAME_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowWarmTransferSipCallTo: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO\"),\n\t\tWorkflowWarmTransferSipTrunkID: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_TRUNK_ID\"),\n\t\tWorkflowWarmTransferSipConnection: sipOutboundConfigFromEnv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CONNECTION_JSON\"),\n\t\tWorkflowWarmTransferSipNumber: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_NUMBER\"),\n\t\tWorkflowWarmTransferSipHeaders: splitEnvStringMap(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_HEADERS\"),\n\t\tWorkflowWarmTransferDTMF: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DTMF\"),\n\t\tWorkflowWarmTransferRingingTimeout: getenvOptionalFloat(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_RINGING_TIMEOUT_SECONDS\"),\n\t\tWorkflowWarmTransferHoldAudio: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferDisableHoldAudio: getenvBool(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_DISABLE_HOLD_AUDIO\"),\n\t\tWorkflowWarmTransferPersona: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_PERSONA\"),\n\t\tWorkflowWarmTransferExtraInstructions: os.Getenv(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_EXTRA_INSTRUCTIONS\"),\n\t\tWorkflowTaskGroupTasks: splitEnvList(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS\"),\n\t\tEvalJudges: splitEnvList(\"RTP_AGENT_EVAL_JUDGES\"),\n\t}\n}\n\nfunc Init(cfg AppConfig) (*App, error) {\n\treturn NewApp(cfg)\n}\n\nfunc NewApp(cfg AppConfig) (*App, error) {\n\tif cfg.Logger != nil {\n\t\tlogutil.SetLogger(cfg.Logger)\n\t}\n\tmetricsRegistry := cfg.MetricsRegistry\n\tif metricsRegistry == nil {\n\t\tmetricsRegistry = telemetry.NewMetricRegistry()\n\t}\n\n\tbaseAgent := agent.NewAgent(cfg.Instructions)\n\tif baseAgent.Instructions == \"\" {\n\t\tbaseAgent.Instructions = \"You are a helpful realtime voice agent.\"\n\t}\n\tif len(cfg.InitialChatContext) > 0 {\n\t\tchatCtx, err := llm.ChatContextFromDict(cfg.InitialChatContext)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid RTP_AGENT_CHAT_CONTEXT_JSON: %w\", err)\n\t\t}\n\t\tbaseAgent.ChatCtx = chatCtx\n\t}\n\n\tif err := configureVAD(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\trealtimeModel, err := configureProviders(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureSTTAdapters(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := configureTurnDetector(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerXAI {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, xaiProviderTools(cfg)...)\n\t}\n\tif normalizeProvider(cfg.LLMProvider) == providerAnthropic {\n\t\tbaseAgent.Tools = append(baseAgent.Tools, anthropicProviderTools(cfg)...)\n\t}\n\tif err := configureAvatar(cfg, baseAgent); err != nil {\n\t\treturn nil, err\n\t}\n\tsessionAgent, err := workflowAgentFromConfig(cfg, baseAgent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionOptions, err := agentSessionOptionsFromConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession := agent.NewAgentSession(sessionAgent, nil, sessionOptions)\n\tif baseAgent.ChatCtx != nil {\n\t\tsession.ChatCtx = baseAgent.ChatCtx.Copy()\n\t}\n\tif realtimeModel != nil {\n\t\tsession.RealtimeModel = realtimeModel\n\t\tsession.Assistant = agent.NewMultimodalAgent(realtimeModel, session.ChatCtx)\n\t}\n\tmcpServers, err := configureMCPTools(context.Background(), cfg, sessionAgent.GetAgent())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsession.SetMCPServers(mcpServers)\n\tif err := configureAppTools(cfg, sessionAgent.GetAgent(), session); err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\tevaluator, err := evaluatorFromConfig(cfg, session.LLM)\n\tif err != nil {\n\t\tcloseMCPServers(mcpServers)\n\t\treturn nil, err\n\t}\n\n\topts := cfg.WorkerOptions\n\tserver := worker.NewAgentServer(opts)\n\topts = server.Options\n\tif opts.AgentName == \"\" {\n\t\topts.AgentName = \"example-agent\"\n\t\tserver.Options.AgentName = opts.AgentName\n\t}\n\tif opts.WorkerType == \"\" && opts.Transport != worker.WorkerTransportAgora {\n\t\topts.WorkerType = worker.WorkerTypeRoom\n\t\tserver.Options.WorkerType = opts.WorkerType\n\t}\n\tsession.MetricsCollector = metricsRegistry.GetUsageCollector(telemetry.MetricLabels{AgentName: opts.AgentName})\n\n\tapp := &App{\n\t\tServer: server,\n\t\tAgent: sessionAgent.GetAgent(),\n\t\tSession: session,\n\t\tRealtimeModel: realtimeModel,\n\t\tEvaluator: evaluator,\n\t\tMCPServers: mcpServers,\n\t\tMetricsRegistry: metricsRegistry,\n\t\tConfig: cfg,\n\t}\n\tif opts.Transport == worker.WorkerTransportAgora {\n\t\tserver.SetTransportRunFunc(app.runAgora)\n\t}\n\tif cfg.TelemetryLogsEndpoint != \"\" {\n\t\tif err := appInitLoggerProvider(context.Background(), cfg.TelemetryLogsEndpoint, cfg.TelemetryLogsHeaders); err != nil {\n\t\t\tapp.closeMCPServers()\n\t\t\treturn nil, err\n\t\t}\n\t\tapp.telemetryLogs = true\n\t}\n\tif err := server.RTCSession(app.runSession, nil, nil); err != nil {\n\t\t_ = app.Close(context.Background())\n\t\treturn nil, err\n\t}\n\treturn app, nil\n}\n\nfunc (a *App) runAgora(ctx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trunCtx, cancelRun := context.WithCancelCause(ctx)\n\tdefer cancelRun(nil)\n\tagoraOpts := a.Config.Agora\n\tif err := agoraOpts.Validate(); err != nil {\n\t\treturn err\n\t}\n\tclient, err := appNewAgoraChannelClient()\n\tif err != nil {\n\t\treturn err\n\t}\n\ttransport := workeragora.NewTransport(agoraOpts, client)\n\taudioInput := workeragora.NewAudioInput(runCtx, a.Session)\n\ttransport.SetAudioHandler(audioInput.HandleAudioFrame)\n\tagoraEvents := &agoraRuntimeEventHandler{\n\t\tctx: runCtx,\n\t\tsession: a.Session,\n\t\tgreeting: a.Config.AgoraGreeting,\n\t}\n\tif workeragora.DataEnabled(agoraOpts) {\n\t\tdataOpts, err := workeragora.ResolveDataOptions(agoraOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataPublisher, err := appNewAgoraDataPublisher(dataOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tagoraEvents.publishGreetingTranscript = func(ctx context.Context, text string) error {\n\t\t\treturn workeragora.PublishTranscript(ctx, dataPublisher, \"assistant\", text, true, \"100\", time.Now())\n\t\t}\n\t\ttranscriptForwarder := workeragora.NewTranscriptForwarder(a.Session, dataPublisher, workeragora.TranscriptForwarderOptions{\n\t\t\tUserStreamID: dataOpts.RemoteStreamID,\n\t\t})\n\t\tvar dataSubscriber workeragora.DataMessageSubscriber\n\t\tif subscriber, ok := dataPublisher.(workeragora.DataMessageSubscriber); ok {\n\t\t\tdataSubscriber = subscriber\n\t\t\tinstallAgoraRTMDataMessageHandler(runCtx, dataSubscriber, a.Session, dataOpts.UID, dataOpts.Channel)\n\t\t}\n\t\ttranscriptForwarder.Start(runCtx)\n\t\tdefer func() {\n\t\t\tif dataSubscriber != nil {\n\t\t\t\tdataSubscriber.SetDataMessageHandler(nil)\n\t\t\t}\n\t\t\tif err := transcriptForwarder.Stop(context.Background()); err != nil {\n\t\t\t\tlogutil.Logger.Errorw(\"failed to close Agora data publisher\", err)\n\t\t\t}\n\t\t}()\n\t}\n\teventsCtx, stopObservingEvents := context.WithCancel(context.Background())\n\teventsDone := make(chan struct{})\n\tgo func() {\n\t\tdefer close(eventsDone)\n\t\tobserveAgoraTransportEvents(eventsCtx, transport.Events(), cancelRun, agoraEvents.Handle)\n\t}()\n\tif err := transport.Join(runCtx); err != nil {\n\t\tif closeErr := transport.Close(context.Background()); closeErr != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport after join error\", closeErr)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := transport.Close(context.Background()); err != nil {\n\t\t\tlogutil.Logger.Errorw(\"failed to close Agora transport\", err)\n\t\t}\n\t\t<-eventsDone\n\t\tstopObservingEvents()\n\t}()\n\tif workeragora.PublishAudioEnabled(agoraOpts.PublishAudio) {\n\t\taudioOutput := workeragora.NewAudioOutput(transport)\n\t\tassistant := a.Session.EnsureAssistant()\n\t\tassistant.SetPublishAudio(func(ctx context.Context, frame *model.AudioFrame) error {\n\t\t\treturn audioOutput.PublishAudio(ctx, frame)\n\t\t})\n\t\tdefer assistant.SetPublishAudio(nil)\n\t}\n\tif err := a.runSessionWithContext(nil, runCtx); err != nil {\n\t\tif cause := context.Cause(runCtx); cause != nil && !errors.Is(cause, context.Canceled) && !errors.Is(cause, context.DeadlineExceeded) {\n\t\t\treturn cause\n\t\t}\n\t\treturn err\n\t}\n\tagoraEvents.FlushPendingGreeting(runCtx)\n\t<-runCtx.Done()\n\treturn runContextErr(runCtx)\n}\n\nfunc runContextErr(ctx context.Context) error {\n\tif cause := context.Cause(ctx); cause != nil {\n\t\treturn cause\n\t}\n\treturn ctx.Err()\n}\n\nfunc normalizeAgoraRuntimeContext(ctx context.Context) context.Context {\n\tif ctx == nil {\n\t\treturn context.Background()\n\t}\n\treturn ctx\n}\n\ntype agoraRTMCallbackMergedContext struct {\n\tcontext.Context\n\tcallback context.Context\n}\n\nfunc (c agoraRTMCallbackMergedContext) Deadline() (time.Time, bool) {\n\truntimeDeadline, runtimeOK := c.Context.Deadline()\n\tcallbackDeadline, callbackOK := c.callback.Deadline()\n\tswitch {\n\tcase !runtimeOK:\n\t\treturn callbackDeadline, callbackOK\n\tcase !callbackOK:\n\t\treturn runtimeDeadline, true\n\tcase callbackDeadline.Before(runtimeDeadline):\n\t\treturn callbackDeadline, true\n\tdefault:\n\t\treturn runtimeDeadline, true\n\t}\n}\n\nfunc (c agoraRTMCallbackMergedContext) Err() error {\n\tif err := c.Context.Err(); err != nil {\n\t\treturn err\n\t}\n\treturn c.callback.Err()\n}\n\nfunc (c agoraRTMCallbackMergedContext) Value(key any) any {\n\tif value := c.callback.Value(key); value != nil {\n\t\treturn value\n\t}\n\treturn c.Context.Value(key)\n}\n\nfunc agoraRTMCallbackContext(runtimeCtx context.Context, callbackCtx context.Context) (context.Context, context.CancelFunc) {\n\truntimeCtx = normalizeAgoraRuntimeContext(runtimeCtx)\n\tif callbackCtx == nil {\n\t\treturn runtimeCtx, func() {}\n\t}\n\tctx, cancel := context.WithCancel(runtimeCtx)\n\tstopCallbackCancel := context.AfterFunc(callbackCtx, cancel)\n\treturn agoraRTMCallbackMergedContext{Context: ctx, callback: callbackCtx}, func() {\n\t\tstopCallbackCancel()\n\t\tcancel()\n\t}\n}\n\nfunc installAgoraRTMDataMessageHandler(ctx context.Context, subscriber workeragora.DataMessageSubscriber, responder workeragora.TextResponder, agentUserID string, channel string) {\n\tif subscriber == nil {\n\t\treturn\n\t}\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\trouter := workeragora.RTMMessageRouter{\n\t\tAgentUserID: agentUserID,\n\t\tChannel: channel,\n\t\tTextInput: func(ctx context.Context, ev workeragora.TextInputEvent) error {\n\t\t\tif err := workeragora.HandleTextInputEvent(ctx, responder, ev); err != nil {\n\t\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM text input\", err, \"channel\", ev.Channel, \"publisher\", ev.Publisher)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\tsubscriber.SetDataMessageHandler(func(callbackCtx context.Context, msg workeragora.DataMessage) error {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tif callbackCtx != nil {\n\t\t\tselect {\n\t\t\tcase <-callbackCtx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\trouteCtx, cancelRoute := agoraRTMCallbackContext(ctx, callbackCtx)\n\t\tdefer cancelRoute()\n\t\tif err := router.HandleDataMessage(routeCtx, msg); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to handle Agora RTM data message\", err, \"channel\", msg.Channel, \"publisher\", msg.Publisher)\n\t\t}\n\t\treturn nil\n\t})\n}\n\ntype agoraRuntimeEventHandler struct {\n\tctx context.Context\n\tsession *agent.AgentSession\n\tgreeting string\n\tpublishGreetingTranscript func(context.Context, string) error\n\tmu sync.Mutex\n\tusers int\n\tpendingGreeting bool\n}\n\nfunc (h *agoraRuntimeEventHandler) Handle(event workeragora.Event) {\n\tif h == nil {\n\t\treturn\n\t}\n\tswitch event.Kind {\n\tcase workeragora.EventUserJoined:\n\t\tctx := normalizeAgoraRuntimeContext(h.ctx)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\t\th.mu.Lock()\n\t\th.users++\n\t\tshouldGreet := h.users == 1 && h.greeting != \"\" && h.session != nil\n\t\th.mu.Unlock()\n\t\tif shouldGreet && !h.sayAndPublishGreeting(ctx, event) {\n\t\t\th.mu.Lock()\n\t\t\th.pendingGreeting = true\n\t\t\th.mu.Unlock()\n\t\t}\n\tcase workeragora.EventUserLeft:\n\t\th.mu.Lock()\n\t\tif h.users > 0 {\n\t\t\th.users--\n\t\t}\n\t\tif h.users == 0 {\n\t\t\th.pendingGreeting = false\n\t\t}\n\t\th.mu.Unlock()\n\tcase workeragora.EventDisconnected, workeragora.EventError:\n\t\th.mu.Lock()\n\t\th.users = 0\n\t\th.pendingGreeting = false\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) FlushPendingGreeting(ctx context.Context) {\n\tif h == nil {\n\t\treturn\n\t}\n\tctx = normalizeAgoraRuntimeContext(ctx)\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tdefault:\n\t}\n\th.mu.Lock()\n\tpending := h.pendingGreeting\n\tif pending {\n\t\th.pendingGreeting = false\n\t}\n\th.mu.Unlock()\n\tif pending && !h.sayAndPublishGreeting(ctx, workeragora.Event{}) {\n\t\th.mu.Lock()\n\t\th.pendingGreeting = true\n\t\th.mu.Unlock()\n\t}\n}\n\nfunc (h *agoraRuntimeEventHandler) sayAndPublishGreeting(ctx context.Context, event workeragora.Event) bool {\n\tif h == nil || h.greeting == \"\" || h.session == nil {\n\t\treturn true\n\t}\n\tif _, err := h.session.Say(ctx, h.greeting); err != nil {\n\t\tif errors.Is(err, agent.ErrAgentSessionNotRunning) {\n\t\t\treturn false\n\t\t}\n\t\tlogutil.Logger.Warnw(\"failed to say Agora greeting\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\treturn true\n\t}\n\tif h.publishGreetingTranscript != nil {\n\t\tif err := h.publishGreetingTranscript(ctx, h.greeting); err != nil {\n\t\t\tlogutil.Logger.Warnw(\"failed to publish Agora greeting transcript\", err, \"channel\", event.Channel, \"userID\", event.UserID)\n\t\t}\n\t}\n\treturn true\n}\n\nfunc observeAgoraTransportEvents(ctx context.Context, events <-chan workeragora.Event, onFatal func(error), onEvent func(workeragora.Event)) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase event, ok := <-events:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlogAgoraTransportEvent(event)\n\t\t\tif onEvent != nil {\n\t\t\t\tonEvent(event)\n\t\t\t}\n\t\t\tif onFatal != nil {\n\t\t\t\tif err := agoraTransportFatalError(event); err != nil {\n\t\t\t\t\tonFatal(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc agoraTransportFatalError(event workeragora.Event) error {\n\tswitch event.Kind {\n\tcase workeragora.EventDisconnected:\n\t\treturn fmt.Errorf(\"agora transport disconnected: channel %s reason %d\", event.Channel, event.Reason)\n\tcase workeragora.EventError:\n\t\tif event.Err != nil {\n\t\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d: %w\", event.Channel, event.Reason, event.Err)\n\t\t}\n\t\treturn fmt.Errorf(\"agora transport event error: channel %s reason %d\", event.Channel, event.Reason)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc logAgoraTransportEvent(event workeragora.Event) {\n\tswitch event.Kind {\n\tcase workeragora.EventConnected:\n\t\tlogutil.Logger.Infow(\"agora transport connected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventDisconnected:\n\t\tlogutil.Logger.Infow(\"agora transport disconnected\", \"channel\", event.Channel, \"reason\", event.Reason)\n\tcase workeragora.EventUserJoined:\n\t\tlogutil.Logger.Infow(\"agora participant joined\", \"channel\", event.Channel, \"userID\", event.UserID)\n\tcase workeragora.EventUserLeft:\n\t\tlogutil.Logger.Infow(\"agora participant left\", \"channel\", event.Channel, \"userID\", event.UserID, \"reason\", event.Reason)\n\tcase workeragora.EventError:\n\t\tlogutil.Logger.Warnw(\"agora transport event error\", event.Err, \"channel\", event.Channel, \"reason\", event.Reason)\n\t}\n}\n\nfunc (a *App) Close(ctx context.Context) error {\n\tif a == nil {\n\t\treturn nil\n\t}\n\ta.closeMCPServers()\n\tif a.telemetryLogs {\n\t\ta.telemetryLogs = false\n\t\treturn appShutdownLoggerProvider(ctx)\n\t}\n\treturn nil\n}\n\nfunc (a *App) EvaluateSession(ctx context.Context, reference *llm.ChatContext) (*EvaluationSummary, error) {\n\tif a == nil || a.Evaluator == nil {\n\t\treturn nil, fmt.Errorf(\"evaluation is not configured\")\n\t}\n\tif a.Session == nil {\n\t\treturn nil, fmt.Errorf(\"agent session is not configured\")\n\t}\n\tctx = a.evaluationContext(ctx)\n\tresult, err := a.Evaluator.Evaluate(ctx, a.Session.ChatCtx, reference)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn evaluationSummaryFromResult(result), nil\n}\n\ntype evaluationTagger interface {\n\tTagger() *agent.Tagger\n}\n\nfunc (a *App) evaluationContext(ctx context.Context) context.Context {\n\tjobCtx, err := a.Session.JobContext()\n\tif err != nil {\n\t\treturn ctx\n\t}\n\ttaggerSource, ok := jobCtx.(evaluationTagger)\n\tif !ok || taggerSource.Tagger() == nil {\n\t\treturn ctx\n\t}\n\treturn evals.WithEvaluationResultHandler(ctx, func(result *evals.EvaluationResult) {\n\t\ttaggerSource.Tagger().Evaluation(evaluationResultForTagger(result))\n\t})\n}\n\nfunc evaluationResultForTagger(result *evals.EvaluationResult) *agent.EvaluationResult {\n\tout := &agent.EvaluationResult{\n\t\tJudgments: make(map[string]string),\n\t\tReasoning: make(map[string]string),\n\t\tInstructions: make(map[string]string),\n\t}\n\tif result == nil {\n\t\treturn out\n\t}\n\tfor name, judgment := range result.Judgments {\n\t\tif judgment != nil {\n\t\t\tout.Judgments[name] = string(judgment.Verdict)\n\t\t\tout.Reasoning[name] = judgment.Reasoning\n\t\t\tout.Instructions[name] = judgment.Instructions\n\t\t}\n\t}\n\treturn out\n}\n\nfunc evaluationSummaryFromResult(result *evals.EvaluationResult) *EvaluationSummary {\n\tif result == nil {\n\t\tresult = &evals.EvaluationResult{}\n\t}\n\treturn &EvaluationSummary{\n\t\tResult: result,\n\t\tScore: result.Score(),\n\t\tAllPassed: result.AllPassed(),\n\t\tAnyPassed: result.AnyPassed(),\n\t\tMajorityPassed: result.MajorityPassed(),\n\t\tNoneFailed: result.NoneFailed(),\n\t}\n}\n\nfunc workflowAgentFromConfig(cfg AppConfig, baseAgent *agent.Agent) (agent.AgentInterface, error) {\n\ttask := normalizeProvider(cfg.WorkflowTask)\n\tif task == \"\" {\n\t\treturn baseAgent, nil\n\t}\n\n\tvar selected agent.AgentInterface\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\tselected = workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t})\n\tcase \"email\", \"get_email\":\n\t\tselected = workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t})\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\tselected = workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t})\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\tselected = workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\tRequireConfirmationSet: true,\n\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t})\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = workflows.NewGetNameTask(nameOpts)\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\tselected = workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\tselected = workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\tselected = workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\tselected = workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\tcase \"dtmf\", \"get_dtmf\":\n\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(workflowDtmfOptionsFromConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = task\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer workflow\")\n\t\t}\n\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID),\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tapplyWarmTransferOptions(task, cfg)\n\t\tselected = task\n\tcase \"task_group\", \"task-group\":\n\t\tselectedGroup, err := workflowTaskGroupFromConfig(cfg, baseAgent)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tselected = selectedGroup\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK %q\", cfg.WorkflowTask)\n\t}\n\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\treturn selected, nil\n}\n\nfunc workflowTaskGroupFromConfig(cfg AppConfig, baseAgent *agent.Agent) (*workflows.TaskGroup, error) {\n\tif len(cfg.WorkflowTaskGroupTasks) == 0 {\n\t\treturn nil, fmt.Errorf(\"RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS is required for task_group workflow\")\n\t}\n\tgroup := workflows.NewTaskGroup(true, false)\n\tfor _, taskName := range cfg.WorkflowTaskGroupTasks {\n\t\tinfo, err := workflowTaskFactoryFromName(cfg, baseAgent, taskName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tgroup.Add(info.ID, info.Description, info.TaskFactory)\n\t}\n\treturn group, nil\n}\n\nfunc workflowInstructionParts(persona, extra string) *beta.InstructionParts {\n\tpersona = strings.TrimSpace(persona)\n\textra = strings.TrimSpace(extra)\n\tif persona == \"\" && extra == \"\" {\n\t\treturn nil\n\t}\n\tparts := &beta.InstructionParts{Extra: extra}\n\tif persona != \"\" {\n\t\tparts.Persona = &persona\n\t}\n\treturn parts\n}\n\nfunc workflowNameOptionsFromConfig(cfg AppConfig) (workflows.GetNameOptions, error) {\n\tfirstName := true\n\tif cfg.WorkflowNameFirstName != nil {\n\t\tfirstName = *cfg.WorkflowNameFirstName\n\t}\n\tmiddleName := false\n\tif cfg.WorkflowNameMiddleName != nil {\n\t\tmiddleName = *cfg.WorkflowNameMiddleName\n\t}\n\tlastName := true\n\tif cfg.WorkflowNameLastName != nil {\n\t\tlastName = *cfg.WorkflowNameLastName\n\t}\n\tif !firstName && !middleName && !lastName {\n\t\treturn workflows.GetNameOptions{}, fmt.Errorf(\"%s\", \"At least one of first_name, middle_name, or last_name must be True\")\n\t}\n\treturn workflows.GetNameOptions{\n\t\tFirstName: firstName,\n\t\tMiddleName: middleName,\n\t\tLastName: lastName,\n\t\tNameFormat: cfg.WorkflowNameFormat,\n\t\tVerifySpelling: cfg.WorkflowNameVerifySpelling,\n\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\tRequireConfirmationSet: true,\n\t\tExtraInstructions: cfg.WorkflowNameExtraInstructions,\n\t}, nil\n}\n\nfunc workflowDtmfOptionsFromConfig(cfg AppConfig) workflows.GetDtmfOptions {\n\tnumDigits := 1\n\tif cfg.WorkflowDtmfNumDigits != nil {\n\t\tnumDigits = *cfg.WorkflowDtmfNumDigits\n\t}\n\taskConfirmation := cfg.WorkflowRequireConfirmation\n\tif cfg.WorkflowDtmfAskConfirmation != nil {\n\t\taskConfirmation = *cfg.WorkflowDtmfAskConfirmation\n\t}\n\topts := workflows.GetDtmfOptions{\n\t\tNumDigits: numDigits,\n\t\tAskForConfirmation: askConfirmation,\n\t\tExtraInstructions: cfg.WorkflowDtmfExtraInstructions,\n\t}\n\tif cfg.WorkflowDtmfInputTimeoutSeconds != nil {\n\t\topts.DtmfInputTimeout = time.Duration(*cfg.WorkflowDtmfInputTimeoutSeconds * float64(time.Second))\n\t}\n\tif stopEvent := strings.TrimSpace(cfg.WorkflowDtmfStopEvent); stopEvent != \"\" {\n\t\topts.DtmfStopEvent = beta.DtmfEvent(stopEvent)\n\t}\n\treturn opts\n}\n\nfunc workflowWarmTransferHoldAudio(cfg AppConfig) interface{} {\n\tif cfg.WorkflowWarmTransferDisableHoldAudio {\n\t\treturn nil\n\t}\n\treturn backgroundAudioSource(cfg.WorkflowWarmTransferHoldAudio)\n}\n\nfunc workflowTaskFactoryFromName(cfg AppConfig, baseAgent *agent.Agent, taskName string) (workflows.FactoryInfo, error) {\n\ttask := normalizeProvider(taskName)\n\tfactory := func(taskFactory func() agent.AgentInterface) func() agent.AgentInterface {\n\t\treturn func() agent.AgentInterface {\n\t\t\tselected := taskFactory()\n\t\t\tcopyAgentRuntime(selected.GetAgent(), baseAgent)\n\t\t\treturn selected\n\t\t}\n\t}\n\tswitch task {\n\tcase \"address\", \"get_address\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"address\",\n\t\t\tDescription: \"Collect and confirm the user's mailing address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetAddressTask(workflows.GetAddressOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowAddressPersona, cfg.WorkflowAddressExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"email\", \"get_email\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"email\",\n\t\t\tDescription: \"Collect and confirm the user's email address.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetEmailTask(workflows.GetEmailOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowEmailPersona, cfg.WorkflowEmailExtraInstructions),\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"phone_number\", \"phone-number\", \"phone\", \"get_phone_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"phone_number\",\n\t\t\tDescription: \"Collect and confirm the user's phone number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetPhoneNumberTask(workflows.GetPhoneNumberOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowPhoneNumberExtraInstructions,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"dob\", \"date_of_birth\", \"date-of-birth\", \"get_dob\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dob\",\n\t\t\tDescription: \"Collect and confirm the user's date of birth.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetDOBTask(workflows.GetDOBOptions{\n\t\t\t\t\tRequireConfirmation: cfg.WorkflowRequireConfirmation,\n\t\t\t\t\tRequireConfirmationSet: true,\n\t\t\t\t\tExtraInstructions: cfg.WorkflowDOBExtraInstructions,\n\t\t\t\t\tIncludeTime: cfg.WorkflowDOBIncludeTime,\n\t\t\t\t})\n\t\t\t}),\n\t\t}, nil\n\tcase \"name\", \"get_name\":\n\t\tnameOpts, err := workflowNameOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"name\",\n\t\t\tDescription: \"Collect and confirm the user's name.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetNameTask(nameOpts)\n\t\t\t}),\n\t\t}, nil\n\tcase \"card_number\", \"card-number\", \"get_card_number\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"card_number\",\n\t\t\tDescription: \"Collect and validate the user's credit card number.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCardNumberTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"security_code\", \"security-code\", \"get_security_code\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"security_code\",\n\t\t\tDescription: \"Collect and validate the user's card security code.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetSecurityCodeTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"expiration_date\", \"expiration-date\", \"get_expiration_date\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"expiration_date\",\n\t\t\tDescription: \"Collect and validate the user's card expiration date.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetExpirationDateTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"credit_card\", \"credit-card\", \"get_credit_card\":\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"credit_card\",\n\t\t\tDescription: \"Collect and validate the user's credit card details.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\treturn workflows.NewGetCreditCardTask(cfg.WorkflowRequireConfirmation)\n\t\t\t}),\n\t\t}, nil\n\tcase \"dtmf\", \"get_dtmf\":\n\t\topts := workflowDtmfOptionsFromConfig(cfg)\n\t\tif err := workflows.ValidateDtmfNumDigits(opts.NumDigits); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"dtmf\",\n\t\t\tDescription: \"Collect DTMF inputs from the user.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewGetDtmfTaskWithOptions(opts)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated DTMF task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tcase \"warm_transfer\", \"warm-transfer\":\n\t\tsipCallTo := strings.TrimSpace(cfg.WorkflowWarmTransferSipCallTo)\n\t\tif sipCallTo == \"\" {\n\t\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"RTP_AGENT_WORKFLOW_WARM_TRANSFER_SIP_CALL_TO is required for warm_transfer task group entry\")\n\t\t}\n\t\tsipTrunkID := strings.TrimSpace(cfg.WorkflowWarmTransferSipTrunkID)\n\t\tif sipTrunkID == \"\" && cfg.WorkflowWarmTransferSipConnection == nil {\n\t\t\tsipTrunkID = strings.TrimSpace(os.Getenv(\"LIVEKIT_SIP_OUTBOUND_TRUNK\"))\n\t\t}\n\t\tif _, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\tTargetPhone: sipCallTo,\n\t\t\tTrunkID: sipTrunkID,\n\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t}); err != nil {\n\t\t\treturn workflows.FactoryInfo{}, err\n\t\t}\n\t\treturn workflows.FactoryInfo{\n\t\t\tID: \"warm_transfer\",\n\t\t\tDescription: \"Transfer the caller to a human agent by SIP.\",\n\t\t\tTaskFactory: factory(func() agent.AgentInterface {\n\t\t\t\ttask, err := workflows.NewWarmTransferTaskWithOptions(workflows.WarmTransferOptions{\n\t\t\t\t\tTargetPhone: sipCallTo,\n\t\t\t\t\tTrunkID: sipTrunkID,\n\t\t\t\t\tSipConnection: cfg.WorkflowWarmTransferSipConnection,\n\t\t\t\t\tSipNumber: cfg.WorkflowWarmTransferSipNumber,\n\t\t\t\t\tHoldAudio: workflowWarmTransferHoldAudio(cfg),\n\t\t\t\t\tDisableHoldAudio: cfg.WorkflowWarmTransferDisableHoldAudio,\n\t\t\t\t\tChatContext: baseAgent.ChatCtx,\n\t\t\t\t\tInstructions: workflowInstructionParts(cfg.WorkflowWarmTransferPersona, cfg.WorkflowWarmTransferExtraInstructions),\n\t\t\t\t})\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(fmt.Sprintf(\"validated warm transfer task config rejected: %v\", err))\n\t\t\t\t}\n\t\t\t\tapplyWarmTransferOptions(task, cfg)\n\t\t\t\treturn task\n\t\t\t}),\n\t\t}, nil\n\tdefault:\n\t\treturn workflows.FactoryInfo{}, fmt.Errorf(\"unsupported RTP_AGENT_WORKFLOW_TASK_GROUP_TASKS entry %q\", taskName)\n\t}\n}\n\nfunc applyWarmTransferOptions(task *workflows.WarmTransferTask, cfg AppConfig) {\n\tif len(cfg.WorkflowWarmTransferSipHeaders) > 0 {\n\t\ttask.SipHeaders = cfg.WorkflowWarmTransferSipHeaders\n\t}\n\ttask.Dtmf = strings.TrimSpace(cfg.WorkflowWarmTransferDTMF)\n\tif cfg.WorkflowWarmTransferRingingTimeout != nil {\n\t\ttask.RingingTimeout = time.Duration(*cfg.WorkflowWarmTransferRingingTimeout * float64(time.Second))\n\t}\n}\n\nfunc evaluatorFromConfig(cfg AppConfig, evaluatorLLM llm.LLM) (*evals.JudgeGroup, error) {\n\tif len(cfg.EvalJudges) == 0 {\n\t\treturn nil, nil\n\t}\n\tjudges := make([]evals.Evaluator, 0, len(cfg.EvalJudges))\n\tfor _, judgeName := range cfg.EvalJudges {\n\t\tjudge, err := evalJudgeFromName(judgeName, evaluatorLLM)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjudges = append(judges, judge)\n\t}\n\treturn evals.NewJudgeGroup(evaluatorLLM, judges), nil\n}\n\nfunc evalJudgeFromName(name string, evaluatorLLM llm.LLM) (evals.Evaluator, error) {\n\tswitch normalizeProvider(name) {\n\tcase \"task_completion\", \"task-completion\":\n\t\treturn evals.TaskCompletionJudge(evaluatorLLM), nil\n\tcase \"accuracy\":\n\t\treturn evals.AccuracyJudge(evaluatorLLM), nil\n\tcase \"relevancy\", \"relevance\":\n\t\treturn evals.RelevancyJudge(evaluatorLLM), nil\n\tcase \"safety\":\n\t\treturn evals.SafetyJudge(evaluatorLLM), nil\n\tcase \"coherence\":\n\t\treturn evals.CoherenceJudge(evaluatorLLM), nil\n\tcase \"conciseness\":\n\t\treturn evals.ConcisenessJudge(evaluatorLLM), nil\n\tcase \"handoff\":\n\t\treturn evals.HandoffJudge(evaluatorLLM), nil\n\tcase \"tool_use\", \"tool-use\":\n\t\treturn evals.ToolUseJudge(evaluatorLLM), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_EVAL_JUDGES entry %q\", name)\n\t}\n}\n\nfunc copyAgentRuntime(dst *agent.Agent, src *agent.Agent) {\n\tif dst == nil || src == nil {\n\t\treturn\n\t}\n\tdst.ChatCtx = src.ChatCtx\n\tdst.TurnDetection = src.TurnDetection\n\tdst.TurnDetector = src.TurnDetector\n\tdst.AudioTurnDetector = src.AudioTurnDetector\n\tdst.Avatar = src.Avatar\n\tdst.STT = src.STT\n\tdst.VAD = src.VAD\n\tdst.LLM = src.LLM\n\tdst.TTS = src.TTS\n\tdst.AllowInterruptions = src.AllowInterruptions\n\tdst.MinConsecutiveSpeechDelay = src.MinConsecutiveSpeechDelay\n\tdst.UseTTSAlignedTranscript = src.UseTTSAlignedTranscript\n\tdst.MinEndpointingDelay = src.MinEndpointingDelay\n\tdst.MaxEndpointingDelay = src.MaxEndpointingDelay\n\tdst.Tools = append(src.Tools, dst.Tools...)\n}\n\nfunc (a *App) runSession(ctx *worker.JobContext) error {\n\treturn a.runSessionWithContext(ctx, context.Background())\n}\n\nfunc (a *App) runSessionWithContext(ctx *worker.JobContext, sessionCtx context.Context) error {\n\tif a.Session == nil {\n\t\treturn fmt.Errorf(\"agent session is not configured\")\n\t}\n\tif sessionCtx == nil {\n\t\tsessionCtx = context.Background()\n\t}\n\tdefer a.closeMCPServers()\n\tif ctx != nil {\n\t\tctx.SetPrimarySession(a.Session)\n\t\ta.Session.SetJobContext(ctx)\n\t}\n\ta.configureMetricsCollector(ctx)\n\ta.Server.SetConsoleSession(a.Session)\n\tif a.Session.STT == nil && a.Session.LLM == nil && a.Session.TTS == nil && a.RealtimeModel == nil {\n\t\tif ctx != nil {\n\t\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\tif ctx != nil {\n\t\troomOptions := a.RoomOptions\n\t\tif roomOptions.DeleteRoom == nil {\n\t\t\troomOptions.DeleteRoom = func(deleteCtx context.Context, roomName string) error {\n\t\t\t\t_, err := ctx.DeleteRoom(deleteCtx, roomName)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tvar roomIO *workerlivekit.RoomIO\n\t\tif ctx.Room == nil {\n\t\t\troomIO = workerlivekit.NewRoomIO(nil, a.Session, roomOptions)\n\t\t\troom := ctx.NewRoom(roomIO.GetCallback())\n\t\t\troomIO.AttachRoom(room)\n\t\t\tif err := ctx.ConnectPreparedRoom(context.Background(), room); err != nil {\n\t\t\t\t_ = roomIO.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tif ctx.Room != nil {\n\t\t\ta.Session.Room = ctx.Room\n\t\t\tif roomIO == nil {\n\t\t\t\troomIO = workerlivekit.NewRoomIO(ctx.Room, a.Session, roomOptions)\n\t\t\t}\n\t\t\ta.RoomIO = roomIO\n\t\t\tif err := a.startAudioRecorder(ctx, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := configureRoomTools(a.Config, a.Agent, roomIO); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif ctx.Room.LocalParticipant != nil && ctx.Room.ConnectionState() == lksdk.ConnectionStateConnected {\n\t\t\t\tif err := roomIO.Start(context.Background()); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif ctx != nil {\n\t\tinfo := ctx.AvatarStartInfo()\n\t\tif info.LiveKitURL != \"\" && info.LiveKitToken != \"\" {\n\t\t\tsessionCtx = agent.ContextWithAvatarStartInfo(sessionCtx, info)\n\t\t}\n\t}\n\tif err := a.Session.Start(sessionCtx); err != nil {\n\t\treturn err\n\t}\n\tif ctx != nil {\n\t\ta.populateRecorderSessionReport(ctx)\n\t\tif _, err := ctx.MakeSessionReport(a.Session); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (a *App) startAudioRecorder(ctx *worker.JobContext, roomIO *workerlivekit.RoomIO) error {\n\tif ctx == nil || roomIO == nil || roomIO.Recorder == nil || ctx.Report == nil {\n\t\treturn nil\n\t}\n\tif !ctx.Report.RecordingOptions.Audio || ctx.SessionDirectory() == \"\" {\n\t\treturn nil\n\t}\n\treturn roomIO.Recorder.Start(filepath.Join(ctx.SessionDirectory(), \"audio.ogg\"), 48000)\n}\n\nfunc (a *App) populateRecorderSessionReport(ctx *worker.JobContext) {\n\tif ctx == nil || ctx.Report == nil || a == nil || a.RoomIO == nil || a.RoomIO.Recorder == nil {\n\t\treturn\n\t}\n\ta.RoomIO.Recorder.PopulateSessionReport(ctx.Report)\n}\n\nfunc (a *App) configureMetricsCollector(ctx *worker.JobContext) {\n\tif a == nil || a.Session == nil || a.Server == nil || a.MetricsRegistry == nil {\n\t\treturn\n\t}\n\tlabels := telemetry.MetricLabels{AgentName: a.Server.Options.AgentName}\n\tif ctx != nil && ctx.Job != nil {\n\t\tmetricInfo := workerlivekit.JobMetricInfo(ctx.Job)\n\t\tlabels.RoomName = metricInfo.RoomName\n\t\tlabels.ParticipantIdentity = ctx.ParticipantIdentity()\n\t}\n\ta.Session.MetricsCollector = a.MetricsRegistry.GetUsageCollector(labels)\n}\n\nfunc (a *App) closeMCPServers() {\n\tif a == nil {\n\t\treturn\n\t}\n\tcloseMCPServers(a.MCPServers)\n\ta.MCPServers = nil\n}\n\nfunc closeMCPServers(servers []llm.MCPServer) {\n\tfor _, server := range servers {\n\t\tif server != nil {\n\t\t\t_ = server.Close()\n\t\t}\n\t}\n}\n\nfunc configureAvatar(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.AvatarProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerAnam:\n\t\ta.Avatar = anam.NewAnamAvatar(cfg.AnamAPIKey)\n\t\treturn nil\n\tcase providerAvatario:\n\t\ta.Avatar = avatario.NewAvatarioAvatar(cfg.AvatarioAPIKey)\n\t\treturn nil\n\tcase providerAvatarTalk:\n\t\ta.Avatar = avatartalk.NewAvatartalkAvatar(cfg.AvatarTalkAPIKey)\n\t\treturn nil\n\tcase providerBey:\n\t\tavatar, err := bey.NewBeyAvatar(cfg.BeyAPIKey)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerBitHuman:\n\t\ta.Avatar = bithuman.NewBithumanAvatar(cfg.BitHumanAPIKey)\n\t\treturn nil\n\tcase providerDID:\n\t\ta.Avatar = did.NewDIDAvatar(cfg.DIDAgentID, cfg.DIDAPIKey)\n\t\treturn nil\n\tcase providerHedra:\n\t\ta.Avatar = hedra.NewHedraAvatar(cfg.HedraAPIKey)\n\t\treturn nil\n\tcase providerKeyframe:\n\t\ta.Avatar = keyframe.NewKeyframeAgent(cfg.KeyframeAPIKey)\n\t\treturn nil\n\tcase providerLemonSlice:\n\t\ta.Avatar = lemonslice.NewLemonsliceAvatar(cfg.LemonSliceAPIKey)\n\t\treturn nil\n\tcase providerLiveAvatar:\n\t\ta.Avatar = liveavatar.NewLiveAvatar(cfg.LiveAvatarAPIKey)\n\t\treturn nil\n\tcase providerRunway:\n\t\topts := []runway.RunwayAvatarOption{}\n\t\tif cfg.RunwayAvatarID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayAvatarID(cfg.RunwayAvatarID))\n\t\t}\n\t\tif cfg.RunwayPresetID != \"\" {\n\t\t\topts = append(opts, runway.WithRunwayPresetID(cfg.RunwayPresetID))\n\t\t}\n\t\tif cfg.RunwayMaxDuration != nil {\n\t\t\topts = append(opts, runway.WithRunwayMaxDuration(*cfg.RunwayMaxDuration))\n\t\t}\n\t\tavatar, err := runway.NewRunwayAvatar(cfg.RunwayAPISecret, opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.Avatar = avatar\n\t\treturn nil\n\tcase providerSimli:\n\t\ta.Avatar = simli.NewSimliAvatar(cfg.SimliAPIKey)\n\t\treturn nil\n\tcase providerTavus:\n\t\ta.Avatar = tavus.NewTavusAvatar(cfg.TavusAPIKey)\n\t\treturn nil\n\tcase providerTrugen:\n\t\ta.Avatar = trugen.NewTrugenAvatar(cfg.TrugenAPIKey)\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_AVATAR_PROVIDER %q\", cfg.AvatarProvider)\n\t}\n}\n\nfunc configureVAD(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.VADProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase providerSilero:\n\t\tvadOpts := []silero.VADOption{}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, silero.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = silero.NewSileroVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := silero.NewSileroVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tcase providerTen:\n\t\tvadOpts := []ten.VADOption{}\n\t\tif modelPath, ok := existingTenModelPath(); ok {\n\t\t\tvadOpts = append(vadOpts, ten.WithModelPath(modelPath), ten.WithNativeFallback())\n\t\t}\n\t\tif cfg.VADMinSpeechDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSpeechDuration(*cfg.VADMinSpeechDuration))\n\t\t}\n\t\tif cfg.VADMinSilenceDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMinSilenceDuration(*cfg.VADMinSilenceDuration))\n\t\t}\n\t\tif cfg.VADPrefixPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPrefixPaddingDuration(*cfg.VADPrefixPaddingDuration))\n\t\t}\n\t\tif cfg.VADPaddingDuration != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithPaddingDuration(*cfg.VADPaddingDuration))\n\t\t}\n\t\tif cfg.VADMaxBufferedSpeech != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithMaxBufferedSpeech(*cfg.VADMaxBufferedSpeech))\n\t\t}\n\t\tif cfg.VADActivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithActivationThreshold(*cfg.VADActivationThreshold))\n\t\t}\n\t\tif cfg.VADDeactivationThreshold != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithDeactivationThreshold(*cfg.VADDeactivationThreshold))\n\t\t}\n\t\tif cfg.VADSampleRate != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithSampleRate(*cfg.VADSampleRate))\n\t\t}\n\t\tif cfg.VADUpdateInterval != nil {\n\t\t\tvadOpts = append(vadOpts, ten.WithUpdateInterval(*cfg.VADUpdateInterval))\n\t\t}\n\t\tif len(vadOpts) == 0 {\n\t\t\ta.VAD = ten.NewVAD()\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := ten.NewVADWithOptions(vadOpts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.VAD = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_VAD_PROVIDER %q\", cfg.VADProvider)\n\t}\n}\n\nfunc existingTenModelPath() (string, bool) {\n\tmodelPath, err := ten.ModelPath()\n\tif err != nil {\n\t\treturn \"\", false\n\t}\n\tinfo, err := os.Stat(modelPath)\n\tif err != nil || info.IsDir() {\n\t\treturn \"\", false\n\t}\n\treturn modelPath, true\n}\n\nfunc configureTurnDetector(cfg AppConfig, a *agent.Agent) error {\n\tswitch normalizeProvider(cfg.TurnDetectorProvider) {\n\tcase \"\":\n\t\treturn nil\n\tcase \"llm\":\n\t\tif a.LLM == nil {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_TURN_DETECTOR_PROVIDER=llm requires RTP_AGENT_LLM_PROVIDER\")\n\t\t}\n\t\ta.TurnDetector = agent.NewLLMTurnDetector(a.LLM)\n\t\treturn nil\n\tcase providerPipecat:\n\t\tdetector, err := appNewPipecatSmartTurn()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerCavos:\n\t\tvar opts []cavos.SmartTurnOption\n\t\tif addr := os.Getenv(\"RTP_AGENT_SMART_TURN_GRPC_ADDR\"); addr != \"\" {\n\t\t\topts = append(opts, cavos.WithSmartTurnAddr(addr))\n\t\t}\n\t\tdetector, err := cavos.NewSmartTurn(opts...)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.AudioTurnDetector = detector\n\t\treturn nil\n\tcase providerLiveKit:\n\t\tif remoteEOTURL := os.Getenv(\"LIVEKIT_REMOTE_EOT_URL\"); remoteEOTURL != \"\" {\n\t\t\ta.TurnDetector = adapterlivekit.NewMultilingualModel(\n\t\t\t\tadapterlivekit.WithRemoteInferenceBaseURL(remoteEOTURL),\n\t\t\t)\n\t\t\treturn nil\n\t\t}\n\t\tdetector, err := adapterlivekit.NewLocalMultilingualModel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ta.TurnDetector = detector\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TURN_DETECTOR_PROVIDER %q\", cfg.TurnDetectorProvider)\n\t}\n}\n\nfunc configureSTTAdapters(cfg AppConfig, a *agent.Agent) error {\n\tif cfg.STTMultiSpeaker == nil || !*cfg.STTMultiSpeaker {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_MULTI_SPEAKER=true requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tadapter, err := corestt.NewDefaultMultiSpeakerAdapter(a.STT)\n\tif err != nil {\n\t\treturn err\n\t}\n\ta.STT = adapter\n\treturn nil\n}\n\nfunc configureLLMFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.LLMFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.LLM == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_LLM_FALLBACK_PROVIDERS requires RTP_AGENT_LLM_PROVIDER\")\n\t}\n\tllms := make([]llm.LLM, 0, len(cfg.LLMFallbackProviders)+1)\n\tllms = append(llms, a.LLM)\n\tfor _, provider := range cfg.LLMFallbackProviders {\n\t\tfallback, err := fallbackLLMFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tllms = append(llms, fallback)\n\t}\n\ta.LLM = llm.NewFallbackAdapter(llms)\n\treturn nil\n}\n\nfunc fallbackLLMFromProvider(cfg AppConfig, provider string) (llm.LLM, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel)\n\tcase providerAzure:\n\t\treturn azureLLMFromConfig(cfg)\n\tcase providerCerebras:\n\t\treturn cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel), nil\n\tcase providerFireworks:\n\t\treturn fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel), nil\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\treturn anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\tcase providerGoogle:\n\t\treturn adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\tcase providerBaseten:\n\t\treturn baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\tcase providerLangChain:\n\t\treturn langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel), nil\n\tcase providerMistralAI:\n\t\treturn mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel), nil\n\tcase providerOpenAI:\n\t\treturn openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\tcase providerDeepSeek:\n\t\treturn openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\tcase providerCometAPI:\n\t\treturn openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\tcase providerNebius:\n\t\treturn openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\tcase providerLetta:\n\t\treturn openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\tcase providerOVHCloud:\n\t\treturn openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\tcase providerOctoAI:\n\t\treturn openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\tcase providerOllama:\n\t\treturn openai.NewOllamaOpenAILLM(cfg.LLMModel), nil\n\tcase providerOpenRouter:\n\t\treturn openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\tcase providerSambaNova:\n\t\treturn openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\tcase providerPerplexity:\n\t\treturn perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel), nil\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerTelnyx:\n\t\treturn telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel), nil\n\tcase providerGroq:\n\t\treturn groqLLMFromConfig(cfg), nil\n\tcase providerXAI:\n\t\treturn xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel), nil\n\tcase providerTogether:\n\t\treturn openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\tcase providerLiveKit:\n\t\treturn adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureSTTFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.STTFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.STT == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_STT_FALLBACK_PROVIDERS requires RTP_AGENT_STT_PROVIDER\")\n\t}\n\tstts := make([]corestt.STT, 0, len(cfg.STTFallbackProviders)+1)\n\tstts = append(stts, a.STT)\n\tfor _, provider := range cfg.STTFallbackProviders {\n\t\tfallback, err := fallbackSTTFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tstts = append(stts, fallback)\n\t}\n\tif a.VAD != nil {\n\t\ta.STT = corestt.NewFallbackAdapterWithVAD(stts, a.VAD)\n\t\treturn nil\n\t}\n\ta.STT = corestt.NewFallbackAdapter(stts)\n\treturn nil\n}\n\nfunc deepgramSTTFromConfig(cfg AppConfig) corestt.STT {\n\tversion := strings.TrimSpace(strings.ToLower(cfg.STTVersion))\n\tif version == \"v2\" || version == \"2\" {\n\t\tsttOpts := []deepgram.DeepgramSTTv2Option{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2BaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Model(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2SampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTMIPOptOut != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2MipOptOut(*cfg.STTMIPOptOut))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Keyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif eager := modelOptionFloat(cfg.STTModelOptions, \"eager_eot_threshold\"); eager != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EagerEOTThreshold(*eager))\n\t\t}\n\t\tif eot := modelOptionFloat(cfg.STTModelOptions, \"eot_threshold\"); eot != nil {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTThreshold(*eot))\n\t\t}\n\t\tif timeoutMS := modelOptionInt(cfg.STTModelOptions, \"eot_timeout_ms\"); timeoutMS > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2EOTTimeout(timeoutMS))\n\t\t}\n\t\tif len(cfg.STTTags) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2Tags(cfg.STTTags))\n\t\t}\n\t\tif hints := splitStringList(cfg.STTLanguageOptions); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hint\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if hints := modelOptionStringList(cfg.STTModelOptions, \"language_hints\"); len(hints) > 0 {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints(hints))\n\t\t} else if language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTv2LanguageHints([]string{language}))\n\t\t}\n\t\treturn deepgram.NewDeepgramSTTv2(\"\", sttOpts...)\n\t}\n\n\tsttOpts := []deepgram.DeepgramSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDetectLanguage(true))\n\t}\n\tif cfg.STTInterimResults != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTInterimResults(*cfg.STTInterimResults))\n\t}\n\tif cfg.STTPunctuate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTPunctuate(*cfg.STTPunctuate))\n\t}\n\tif cfg.STTSmartFormat != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSmartFormat(*cfg.STTSmartFormat))\n\t}\n\tif cfg.STTNoDelay != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNoDelay(*cfg.STTNoDelay))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTEndpointing(*cfg.STTEndpointingMS))\n\t}\n\tif cfg.STTDiarization != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTDiarization(*cfg.STTDiarization))\n\t}\n\tif cfg.STTFillerWords != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTFillerWords(*cfg.STTFillerWords))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumChannels(*cfg.STTNumberOfChannels))\n\t}\n\tif cfg.STTVADEvents != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTVADEvents(*cfg.STTVADEvents))\n\t}\n\tif cfg.STTProfanityFilter != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t}\n\tif cfg.STTNumerals != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTNumerals(*cfg.STTNumerals))\n\t}\n\tif cfg.STTMIPOptOut != nil {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTMipOptOut(*cfg.STTMIPOptOut))\n\t}\n\tif len(cfg.STTKeywords) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeywords(cfg.STTKeywords))\n\t}\n\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTKeyterms(cfg.STTKeytermsPrompt))\n\t}\n\tif len(cfg.STTRedact) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTRedact(cfg.STTRedact))\n\t}\n\tif len(cfg.STTTags) > 0 {\n\t\tsttOpts = append(sttOpts, deepgram.WithDeepgramSTTTags(cfg.STTTags))\n\t}\n\treturn deepgram.NewDeepgramSTT(\"\", cfg.STTModel, sttOpts...)\n}\n\nfunc fallbackSTTFromProvider(cfg AppConfig, provider string) (corestt.STT, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerAWS:\n\t\treturn awsSTTFromConfig(cfg)\n\tcase providerAzure:\n\t\treturn azureSTTFromConfig(cfg)\n\tcase providerFal:\n\t\treturn falSTTFromConfig(cfg), nil\n\tcase providerGroq:\n\t\treturn groqSTTFromConfig(cfg)\n\tcase providerSpitch:\n\t\treturn spitch.NewSpitchSTT(cfg.SpitchAPIKey), nil\n\tcase providerDeepgram:\n\t\treturn deepgramSTTFromConfig(cfg), nil\n\tcase providerCavos:\n\t\treturn cavosSTTFromConfig(cfg), nil\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\treturn openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\treturn cartesia.NewCartesiaSTT(\"\", sttOpts...), nil\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\treturn clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), nil\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...), nil\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\treturn fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...), nil\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\treturn gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...), nil\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\treturn inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...), nil\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...), nil\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\treturn nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\treturn gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...), nil\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\treturn gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...), nil\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...), nil\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\treturn soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...), nil\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...), nil\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...), nil\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\treturn xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...), nil\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\treturn provider, nil\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\treturn rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...), nil\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\treturn assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...), nil\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\treturn simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...), nil\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\treturn slng.NewSTT(cfg.SLNGAPIKey, sttOpts...), nil\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\treturn adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc azureSTTFromConfig(cfg AppConfig) (*azure.AzureSTT, error) {\n\tif err := validateAzureSTTConfig(cfg); err != nil {\n\t\treturn nil, err\n\t}\n\tsttOpts := []azure.AzureSTTOption{}\n\tif speechHost := azureSTTSpeechHostFromConfig(cfg); speechHost != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechHost(speechHost))\n\t}\n\tif speechEndpoint := azureSTTSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := strings.TrimSpace(cfg.STTLanguage); language != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguage(language))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"language\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t} else if languages := azureSTTStringListModelOption(cfg.STTModelOptions, \"languages\"); len(languages) > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTLanguages(languages...))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(*cfg.STTSampleRate))\n\t} else if sampleRate := azureSTTIntModelOption(cfg.STTModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSampleRate(sampleRate))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(*cfg.STTNumberOfChannels))\n\t} else if numChannels := azureSTTIntModelOption(cfg.STTModelOptions, \"num_channels\"); numChannels > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTNumChannels(numChannels))\n\t}\n\tif cfg.STTEndpointingMS != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(*cfg.STTEndpointingMS))\n\t} else if silenceTimeout := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_silence_timeout_ms\"); silenceTimeout > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationSilenceTimeout(silenceTimeout))\n\t}\n\tif maxTime := azureSTTIntModelOption(cfg.STTModelOptions, \"segmentation_max_time_ms\"); maxTime > 0 {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationMaxTime(maxTime))\n\t}\n\tif strategy := azureSTTModelOption(cfg.STTModelOptions, \"segmentation_strategy\"); strategy != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTSegmentationStrategy(strategy))\n\t}\n\tif trueText := azureSTTBoolModelOption(cfg.STTModelOptions, \"true_text_post_processing\"); trueText != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTTrueTextPostProcessing(*trueText))\n\t}\n\tif explicitPunctuation := azureSTTBoolModelOption(cfg.STTModelOptions, \"explicit_punctuation\"); explicitPunctuation != nil {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTExplicitPunctuation(*explicitPunctuation))\n\t}\n\tif profanity := azureSTTModelOption(cfg.STTModelOptions, \"profanity\"); profanity != \"\" {\n\t\tsttOpts = append(sttOpts, azure.WithAzureSTTProfanity(profanity))\n\t}\n\treturn azure.NewAzureSTT(\"\", cfg.STTRegion, sttOpts...)\n}\n\nvar newAzureLLM = func(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken string, opts ...azure.AzureLLMOption) (llm.LLM, error) {\n\treturn azure.NewAzureLLM(model, azureEndpoint, azureDeployment, apiVersion, apiKey, azureADToken, opts...)\n}\n\nfunc azureLLMFromConfig(cfg AppConfig) (llm.LLM, error) {\n\tllmOpts := []azure.AzureLLMOption{}\n\tif maxOutputTokens := modelOptionInt(cfg.LLMModelOptions, \"max_output_tokens\"); maxOutputTokens > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMMaxOutputTokens(maxOutputTokens))\n\t}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTemperature(*temperature))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMToolChoice(toolChoice))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif reasoning, ok := cfg.LLMModelOptions[\"reasoning\"].(map[string]any); ok && len(reasoning) > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMReasoning(reasoning))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTopP(*topP))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMServiceTier(serviceTier))\n\t}\n\tif verbosity := modelOptionString(cfg.LLMModelOptions, \"verbosity\"); verbosity != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMVerbosity(verbosity))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMUser(user))\n\t}\n\tif organization := modelOptionString(cfg.LLMModelOptions, \"organization\"); organization != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMOrganization(organization))\n\t}\n\tif project := modelOptionString(cfg.LLMModelOptions, \"project\"); project != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMProject(project))\n\t}\n\tif baseURL := modelOptionString(cfg.LLMModelOptions, \"base_url\"); baseURL != \"\" {\n\t\tllmOpts = append(llmOpts, azure.WithAzureLLMBaseURL(baseURL))\n\t}\n\treturn newAzureLLM(\n\t\tcfg.LLMModel,\n\t\tcfg.LLMBaseURL,\n\t\tmodelOptionString(cfg.LLMModelOptions, \"azure_deployment\"),\n\t\tmodelOptionString(cfg.LLMModelOptions, \"api_version\"),\n\t\t\"\",\n\t\t\"\",\n\t\tllmOpts...,\n\t)\n}\n\nfunc validateAzureSTTConfig(cfg AppConfig) error {\n\tfor _, key := range []string{\"azure_deployment\", \"api_version\"} {\n\t\tif azureSTTModelOption(cfg.STTModelOptions, key) != \"\" {\n\t\t\treturn fmt.Errorf(\"RTP_AGENT_STT_PROVIDER=azure uses Azure Speech websocket STT and does not support %s; remove Azure OpenAI Whisper deployment options or use an OpenAI-compatible STT provider\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc azureSTTSpeechHostFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.STTBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.STTBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_host\"} {\n\t\tif value := azureSTTModelOption(cfg.STTModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureSTTSpeechEndpointFromConfig(cfg AppConfig) string {\n\treturn azureSTTModelOption(cfg.STTModelOptions, \"speech_endpoint\")\n}\n\nfunc azureSTTIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureSTTBoolModelOption(options map[string]any, key string) *bool {\n\tif value := modelOptionBool(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionBool(setting, key)\n}\n\nfunc azureSTTStringListModelOption(options map[string]any, key string) []string {\n\tif values := modelOptionStringList(options, key); len(values) > 0 {\n\t\treturn values\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionStringList(setting, key)\n}\n\nfunc azureSTTModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc azureTTSSpeechEndpointFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSBaseURL) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSBaseURL)\n\t}\n\tfor _, key := range []string{\"azure_endpoint\", \"speech_endpoint\"} {\n\t\tif value := azureTTSModelOption(cfg.TTSModelOptions, key); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSOptionsFromConfig(cfg AppConfig) []azure.AzureTTSOption {\n\tttsOpts := []azure.AzureTTSOption{}\n\tif speechEndpoint := azureTTSSpeechEndpointFromConfig(cfg); speechEndpoint != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSpeechEndpoint(speechEndpoint))\n\t}\n\tif language := azureTTSLanguageFromConfig(cfg); language != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLanguage(language))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(*cfg.TTSSampleRate))\n\t} else if sampleRate := azureTTSIntModelOption(cfg.TTSModelOptions, \"sample_rate\"); sampleRate > 0 {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSSampleRate(sampleRate))\n\t}\n\tif deploymentID := azureTTSModelOption(cfg.TTSModelOptions, \"deployment_id\"); deploymentID != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSDeploymentID(deploymentID))\n\t}\n\tif lexiconURI := azureTTSModelOption(cfg.TTSModelOptions, \"lexicon_uri\"); lexiconURI != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSLexiconURI(lexiconURI))\n\t}\n\tif styleName := azureTTSModelOption(cfg.TTSModelOptions, \"style\"); styleName != \"\" {\n\t\tstyle := azure.AzureTTSStyle{Style: styleName}\n\t\tif degree := azureTTSFloatModelOption(cfg.TTSModelOptions, \"style_degree\"); degree != nil {\n\t\t\tstyle.Degree = *degree\n\t\t}\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSStyle(style))\n\t}\n\tprosody := azure.AzureTTSProsody{\n\t\tRate: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_rate\"),\n\t\tVolume: azureTTSScalarModelOption(cfg.TTSModelOptions, \"prosody_volume\"),\n\t\tPitch: azureTTSModelOption(cfg.TTSModelOptions, \"prosody_pitch\"),\n\t}\n\tif prosody.Rate != \"\" || prosody.Volume != \"\" || prosody.Pitch != \"\" {\n\t\tttsOpts = append(ttsOpts, azure.WithAzureTTSProsody(prosody))\n\t}\n\treturn ttsOpts\n}\n\nfunc azureTTSLanguageFromConfig(cfg AppConfig) string {\n\tif strings.TrimSpace(cfg.TTSLanguage) != \"\" {\n\t\treturn strings.TrimSpace(cfg.TTSLanguage)\n\t}\n\treturn azureTTSModelOption(cfg.TTSModelOptions, \"language\")\n}\n\nfunc azureTTSIntModelOption(options map[string]any, key string) int {\n\tif value := modelOptionInt(options, key); value > 0 {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn modelOptionInt(setting, key)\n}\n\nfunc azureTTSFloatModelOption(options map[string]any, key string) *float64 {\n\tif value := modelOptionFloat(options, key); value != nil {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn modelOptionFloat(setting, key)\n}\n\nfunc azureTTSScalarModelOption(options map[string]any, key string) string {\n\tif value := azureTTSModelOption(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tif value := azureTTSFloatModelOption(options, key); value != nil {\n\t\treturn strconv.FormatFloat(*value, 'f', -1, 64)\n\t}\n\treturn \"\"\n}\n\nfunc azureTTSModelOption(options map[string]any, key string) string {\n\tif value := modelOptionString(options, key); value != \"\" {\n\t\treturn value\n\t}\n\tsetting, ok := options[\"setting\"].(map[string]any)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn modelOptionString(setting, key)\n}\n\nfunc awsSTTFromConfig(cfg AppConfig) (*adapteraws.AWSSTT, error) {\n\tsttOpts := []adapteraws.AWSSTTOption{}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t}\n\tif cfg.STTVocabularyName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyName(cfg.STTVocabularyName))\n\t}\n\tif cfg.STTSessionID != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTSessionID(cfg.STTSessionID))\n\t}\n\tif cfg.STTVocabularyFilterMethod != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterMethod(awstranscribetypes.VocabularyFilterMethod(cfg.STTVocabularyFilterMethod)))\n\t}\n\tif cfg.STTVocabularyFilterName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterName(cfg.STTVocabularyFilterName))\n\t}\n\tif cfg.STTSpeakerLabels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTShowSpeakerLabel(*cfg.STTSpeakerLabels))\n\t}\n\tif cfg.STTEnableChannelIdentification != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnableChannelIdentification(*cfg.STTEnableChannelIdentification))\n\t}\n\tif cfg.STTNumberOfChannels != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTNumberOfChannels(int32(*cfg.STTNumberOfChannels)))\n\t}\n\tif cfg.STTEnablePartialStabilization != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTEnablePartialResultsStabilization(*cfg.STTEnablePartialStabilization))\n\t}\n\tif cfg.STTPartialResultsStability != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPartialResultsStability(awstranscribetypes.PartialResultsStability(cfg.STTPartialResultsStability)))\n\t}\n\tif cfg.STTLanguageModelName != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageModelName(cfg.STTLanguageModelName))\n\t}\n\tif cfg.STTIdentifyLanguage != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyLanguage(*cfg.STTIdentifyLanguage))\n\t}\n\tif cfg.STTIdentifyMultipleLanguages != nil {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTIdentifyMultipleLanguages(*cfg.STTIdentifyMultipleLanguages))\n\t}\n\tif cfg.STTLanguageOptions != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTLanguageOptions(cfg.STTLanguageOptions))\n\t}\n\tif cfg.STTPreferredLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTPreferredLanguage(awstranscribetypes.LanguageCode(cfg.STTPreferredLanguage)))\n\t}\n\tif cfg.STTVocabularyNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyNames(cfg.STTVocabularyNames))\n\t}\n\tif cfg.STTVocabularyFilterNames != \"\" {\n\t\tsttOpts = append(sttOpts, adapteraws.WithAWSSTTVocabularyFilterNames(cfg.STTVocabularyFilterNames))\n\t}\n\treturn adapteraws.NewAWSSTT(context.Background(), cfg.AWSRegion, sttOpts...)\n}\n\nfunc falSTTFromConfig(cfg AppConfig) *fal.FalSTT {\n\tsttOpts := []fal.FalSTTOption{}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTTask != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTTask(cfg.STTTask))\n\t}\n\tif cfg.STTChunkLevel != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTChunkLevel(cfg.STTChunkLevel))\n\t}\n\tif cfg.STTVersion != \"\" {\n\t\tsttOpts = append(sttOpts, fal.WithFalSTTVersion(cfg.STTVersion))\n\t}\n\treturn fal.NewFalSTT(cfg.FalAPIKey, sttOpts...)\n}\n\nfunc groqSTTFromConfig(cfg AppConfig) (*groq.GroqSTT, error) {\n\tsttOpts := []groq.GroqSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTDetectLanguage {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTDetectLanguage(true))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, groq.WithGroqSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn groq.NewGroqSTT(cfg.GroqAPIKey, cfg.STTModel, sttOpts...)\n}\n\nfunc groqLLMFromConfig(cfg AppConfig) *groq.GroqLLM {\n\tllmOpts := []groq.GroqLLMOption{}\n\tif cfg.LLMBaseURL != \"\" {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMBaseURL(cfg.LLMBaseURL))\n\t}\n\tif timeoutMS := modelOptionInt(cfg.LLMModelOptions, \"timeout_ms\"); timeoutMS > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMTimeout(time.Duration(timeoutMS)*time.Millisecond))\n\t}\n\tif maxRetries, ok := modelOptionIntValue(cfg.LLMModelOptions, \"max_retries\"); ok && maxRetries >= 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMMaxRetries(maxRetries))\n\t}\n\tif openAIOpts := groqOpenAILLMOptionsFromConfig(cfg); len(openAIOpts) > 0 {\n\t\tllmOpts = append(llmOpts, groq.WithGroqLLMOptions(openAIOpts...))\n\t}\n\treturn groq.NewGroqLLM(cfg.GroqAPIKey, cfg.LLMModel, llmOpts...)\n}\n\nfunc groqOpenAILLMOptionsFromConfig(cfg AppConfig) []openai.OpenAILLMOption {\n\topts := []openai.OpenAILLMOption{}\n\tif temperature := modelOptionFloat(cfg.LLMModelOptions, \"temperature\"); temperature != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTemperature(*temperature))\n\t}\n\tif topP := modelOptionFloat(cfg.LLMModelOptions, \"top_p\"); topP != nil {\n\t\topts = append(opts, openai.WithOpenAILLMTopP(*topP))\n\t}\n\tif maxCompletionTokens := modelOptionInt(cfg.LLMModelOptions, \"max_completion_tokens\"); maxCompletionTokens > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMaxCompletionTokens(maxCompletionTokens))\n\t}\n\tif parallelToolCalls := modelOptionBool(cfg.LLMModelOptions, \"parallel_tool_calls\"); parallelToolCalls != nil {\n\t\topts = append(opts, openai.WithOpenAILLMParallelToolCalls(*parallelToolCalls))\n\t}\n\tif toolChoice := modelOptionString(cfg.LLMModelOptions, \"tool_choice\"); toolChoice != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMToolChoice(llm.ToolChoice(toolChoice)))\n\t}\n\tif promptCacheKey := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_key\"); promptCacheKey != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheKey(promptCacheKey))\n\t}\n\tif promptCacheRetention := modelOptionString(cfg.LLMModelOptions, \"prompt_cache_retention\"); promptCacheRetention != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMPromptCacheRetention(promptCacheRetention))\n\t}\n\tif metadata := modelOptionStringMap(cfg.LLMModelOptions, \"metadata\"); len(metadata) > 0 {\n\t\topts = append(opts, openai.WithOpenAILLMMetadata(metadata))\n\t}\n\tif reasoningEffort := modelOptionString(cfg.LLMModelOptions, \"reasoning_effort\"); reasoningEffort != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMReasoningEffort(reasoningEffort))\n\t}\n\tif serviceTier := modelOptionString(cfg.LLMModelOptions, \"service_tier\"); serviceTier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMServiceTier(serviceTier))\n\t}\n\tif user := modelOptionString(cfg.LLMModelOptions, \"user\"); user != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMUser(user))\n\t}\n\tif safetyIdentifier := modelOptionString(cfg.LLMModelOptions, \"safety_identifier\"); safetyIdentifier != \"\" {\n\t\topts = append(opts, openai.WithOpenAILLMSafetyIdentifier(safetyIdentifier))\n\t}\n\treturn opts\n}\n\nfunc configureTTSFallbacks(cfg AppConfig, a *agent.Agent) error {\n\tif len(cfg.TTSFallbackProviders) == 0 {\n\t\treturn nil\n\t}\n\tif a.TTS == nil {\n\t\treturn fmt.Errorf(\"RTP_AGENT_TTS_FALLBACK_PROVIDERS requires RTP_AGENT_TTS_PROVIDER\")\n\t}\n\tttss := make([]coretts.TTS, 0, len(cfg.TTSFallbackProviders)+1)\n\tttss = append(ttss, a.TTS)\n\tfor _, provider := range cfg.TTSFallbackProviders {\n\t\tfallback, err := fallbackTTSFromProvider(cfg, provider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tttss = append(ttss, fallback)\n\t}\n\ta.TTS = coretts.NewFallbackAdapter(ttss)\n\treturn nil\n}\n\nfunc ensureSTTStreaming(provider corestt.STT, detector corevad.VAD) corestt.STT {\n\tif provider == nil || provider.Capabilities().Streaming || detector == nil {\n\t\treturn provider\n\t}\n\treturn corestt.NewStreamAdapter(provider, detector)\n}\n\nfunc ensureTTSStreaming(provider coretts.TTS) coretts.TTS {\n\tif provider.Capabilities().Streaming {\n\t\treturn provider\n\t}\n\treturn coretts.NewStreamAdapter(provider)\n}\n\nfunc appTTSSpeedConfigured(cfg AppConfig) bool {\n\treturn cfg.TTSSpeedSet || cfg.TTSSpeed != 0\n}\n\nfunc elevenLabsTTSOptionsFromConfig(cfg AppConfig) []elevenlabs.ElevenLabsTTSOption {\n\tttsOpts := []elevenlabs.ElevenLabsTTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEnableSSMLParsing != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEnableSSMLParsing(*cfg.TTSEnableSSMLParsing))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(cfg.TTSEncoding))\n\t} else if cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsEncoding(fmt.Sprintf(\"pcm_%d\", *cfg.TTSSampleRate)))\n\t}\n\tif cfg.TTSTextNormalization != nil {\n\t\tmode := \"off\"\n\t\tif *cfg.TTSTextNormalization {\n\t\t\tmode = \"on\"\n\t\t}\n\t\tttsOpts = append(ttsOpts, elevenlabs.WithElevenLabsApplyTextNormalization(mode))\n\t}\n\treturn ttsOpts\n}\n\nfunc cavosSTTFromConfig(cfg AppConfig) corestt.STT {\n\tsttOpts := []cavos.STTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTBaseURL(cfg.STTBaseURL))\n\t}\n\tif cfg.STTModel != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTModel(cfg.STTModel))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTPrompt != \"\" {\n\t\tsttOpts = append(sttOpts, cavos.WithSTTPrompt(cfg.STTPrompt))\n\t}\n\treturn cavos.NewSTT(sttOpts...)\n}\n\nfunc cavosTTSFromConfig(cfg AppConfig) coretts.TTS {\n\tttsOpts := []cavos.TTSOption{}\n\tif cfg.TTSBaseURL != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSBaseURL(cfg.TTSBaseURL))\n\t}\n\tif cfg.TTSModel != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSModel(cfg.TTSModel))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSResponseFormat != \"\" {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSResponseFormat(cfg.TTSResponseFormat))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, cavos.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\treturn cavos.NewTTS(ttsOpts...)\n}\n\nfunc googleTTSConfigFromAppConfig(cfg AppConfig) appGoogleTTSConfig {\n\tgoogleCfg := appGoogleTTSConfig{\n\t\tlanguage: cfg.TTSLanguage,\n\t\tvoice: cfg.TTSVoice,\n\t\tmodel: cfg.TTSModel,\n\t\tprompt: cfg.TTSInstructions,\n\t}\n\tif cfg.TTSSpeakingRate != nil {\n\t\tgoogleCfg.speakingRate = *cfg.TTSSpeakingRate\n\t} else if appTTSSpeedConfigured(cfg) {\n\t\tgoogleCfg.speakingRate = cfg.TTSSpeed\n\t}\n\tif cfg.TTSPitch != nil {\n\t\tgoogleCfg.pitch = float64(*cfg.TTSPitch)\n\t}\n\tgoogleCfg.effectsProfileID = modelOptionString(cfg.TTSModelOptions, \"effects_profile_id\")\n\tif volumeGainDB := modelOptionFloat(cfg.TTSModelOptions, \"volume_gain_db\"); volumeGainDB != nil {\n\t\tgoogleCfg.volumeGainDB = *volumeGainDB\n\t}\n\treturn googleCfg\n}\n\nfunc liveKitTTSOptionsFromConfig(cfg AppConfig) ([]adapterlivekit.TTSOption, error) {\n\tttsOpts := []adapterlivekit.TTSOption{}\n\ttokenizer, err := ttsSentenceTokenizer(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tokenizer != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithSentenceTokenizer(tokenizer))\n\t}\n\tif cfg.TTSVoice != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSVoice(cfg.TTSVoice))\n\t}\n\tif cfg.TTSLanguage != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSLanguage(cfg.TTSLanguage))\n\t}\n\tif cfg.TTSEncoding != \"\" {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSEncoding(cfg.TTSEncoding))\n\t}\n\tif cfg.TTSSampleRate != nil {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t}\n\tif len(cfg.TTSModelOptions) > 0 {\n\t\tttsOpts = append(ttsOpts, adapterlivekit.WithTTSExtraKwargs(cfg.TTSModelOptions))\n\t}\n\treturn ttsOpts, nil\n}\n\nfunc fallbackTTSFromProvider(cfg AppConfig, provider string) (coretts.TTS, error) {\n\tswitch normalizeProvider(provider) {\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\treturn azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\treturn baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...), nil\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\treturn cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\treturn cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...), nil\n\tcase providerCavos:\n\t\treturn cavosTTSFromConfig(cfg), nil\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\treturn deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...), nil\n\tcase providerElevenLabs:\n\t\treturn elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\treturn fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...), nil\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\treturn hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...), nil\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\treturn inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\treturn groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\treturn nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\treturn mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\treturn lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...), nil\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\treturn neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...), nil\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...), nil\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\treturn respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...), nil\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\treturn rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\treturn sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...), nil\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\treturn murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\treturn speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\treturn simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\treturn smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...), nil\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\treturn soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...), nil\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...), nil\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\treturn spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerUpliftAI:\n\t\treturn upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice), nil\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\treturn xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\treturn slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...), nil\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\treturn telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...), nil\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_FALLBACK_PROVIDERS entry %q\", provider)\n\t}\n}\n\nfunc configureProviders(cfg AppConfig, a *agent.Agent) (llm.RealtimeModel, error) {\n\tswitch normalizeProvider(cfg.LLMProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := adapteraws.NewAWSLLM(context.Background(), cfg.AWSRegion, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerAzure:\n\t\tprovider, err := azureLLMFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerBaseten:\n\t\tprovider, err := baseten.NewBasetenLLM(\"\", cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGoogle:\n\t\tprovider, err := adaptergoogle.NewGoogleLLM(cfg.GoogleAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerGroq:\n\t\ta.LLM = groqLLMFromConfig(cfg)\n\tcase providerLangChain:\n\t\ta.LLM = langchain.NewLangchainLLM(cfg.LangChainAPIKey, cfg.LLMModel)\n\tcase providerMistralAI:\n\t\ta.LLM = mistralai.NewMistralLLM(cfg.MistralAPIKey, cfg.LLMModel)\n\tcase providerSarvam:\n\t\tllmOpts := []sarvam.SarvamLLMOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tif len(cfg.LLMExtraHeaders) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraHeaders(cfg.LLMExtraHeaders))\n\t\t}\n\t\tif len(cfg.LLMExtraBody) > 0 {\n\t\t\tllmOpts = append(llmOpts, sarvam.WithSarvamLLMExtraBody(cfg.LLMExtraBody))\n\t\t}\n\t\tprovider := sarvam.NewSarvamLLM(cfg.SarvamAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam LLM configuration\")\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTelnyx:\n\t\ta.LLM = telnyx.NewTelnyxLLM(cfg.TelnyxAPIKey, cfg.LLMModel)\n\tcase providerXAI:\n\t\ta.LLM = xai.NewXaiLLM(cfg.XAIAPIKey, cfg.LLMModel)\n\tcase providerCerebras:\n\t\ta.LLM = cerebras.NewCerebrasLLM(cfg.CerebrasAPIKey, cfg.LLMModel)\n\tcase providerFireworks:\n\t\ta.LLM = fireworksai.NewFireworksLLM(cfg.FireworksAPIKey, cfg.LLMModel)\n\tcase providerAnthropic:\n\t\tllmOpts := []anthropic.AnthropicOption{}\n\t\tif cfg.LLMBaseURL != \"\" {\n\t\t\tllmOpts = append(llmOpts, anthropic.WithAnthropicBaseURL(cfg.LLMBaseURL))\n\t\t}\n\t\tprovider, err := anthropic.NewAnthropicLLM(cfg.AnthropicAPIKey, cfg.LLMModel, llmOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOpenAI:\n\t\tprovider, err := openai.NewOpenAILLM(cfg.OpenAIAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerDeepSeek:\n\t\tprovider, err := openai.NewDeepSeekOpenAILLM(cfg.LLMModel, cfg.DeepSeekAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerCometAPI:\n\t\tprovider, err := openai.NewCometAPIOpenAILLM(cfg.LLMModel, cfg.CometAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerNebius:\n\t\tprovider, err := openai.NewNebiusOpenAILLM(cfg.LLMModel, cfg.NebiusAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerLetta:\n\t\tprovider, err := openai.NewLettaOpenAILLM(cfg.LLMModel, cfg.LLMBaseURL, cfg.LettaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOVHCloud:\n\t\tprovider, err := openai.NewOVHCloudOpenAILLM(cfg.LLMModel, cfg.OVHCloudAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOctoAI:\n\t\tprovider, err := openai.NewOctoAIOpenAILLM(cfg.LLMModel, cfg.OctoAIAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerOllama:\n\t\ta.LLM = openai.NewOllamaOpenAILLM(cfg.LLMModel)\n\tcase providerOpenRouter:\n\t\tprovider, err := openai.NewOpenRouterLLM(cfg.OpenRouterAPIKey, cfg.LLMModel)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerSambaNova:\n\t\tprovider, err := openai.NewSambaNovaOpenAILLM(cfg.LLMModel, cfg.SambaNovaAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerTogether:\n\t\tprovider, err := openai.NewTogetherOpenAILLM(cfg.LLMModel, cfg.TogetherAPIKey)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tcase providerPerplexity:\n\t\ta.LLM = perplexity.NewPerplexityLLM(cfg.PerplexityAPIKey, cfg.LLMModel)\n\tcase providerLiveKit:\n\t\tprovider, err := adapterlivekit.NewLiveKitInferenceLLM(cfg.LLMModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.LLM = provider\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_LLM_PROVIDER %q\", cfg.LLMProvider)\n\t}\n\tif err := configureLLMFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.STTProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tprovider, err := awsSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerAzure:\n\t\tprovider, err := azureSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerBaseten:\n\t\tsttOpts := []baseten.BasetenSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTChainID != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTChainID(cfg.STTChainID))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, baseten.WithBasetenSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenSTT(\"\", cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerGoogle:\n\t\tsttOpts := []adaptergoogle.GoogleSTTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSampleRate(int32(*cfg.STTSampleRate)))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTPunctuate(*cfg.STTPunctuate))\n\t\t}\n\t\tif cfg.STTSpokenPunctuation != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTSpokenPunctuation(*cfg.STTSpokenPunctuation))\n\t\t}\n\t\tif cfg.STTProfanityFilter != nil {\n\t\t\tsttOpts = append(sttOpts, adaptergoogle.WithGoogleSTTProfanityFilter(*cfg.STTProfanityFilter))\n\t\t}\n\t\tprovider, err := adaptergoogle.NewGoogleSTT(cfg.GoogleCredentialsFile, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerElevenLabs:\n\t\tsttOpts := []elevenlabs.ElevenLabsSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTagAudioEvents != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTTagAudioEvents(*cfg.STTTagAudioEvents))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTIncludeTimestamps(*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil || cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTServerVAD(elevenlabs.ElevenLabsVADOptions{\n\t\t\t\tVADSilenceThresholdSecs: cfg.STTVADSilenceThresholdSeconds,\n\t\t\t\tVADThreshold: cfg.STTVADThreshold,\n\t\t\t\tMinSpeechDurationMS: cfg.STTMinTurnSilence,\n\t\t\t\tMinSilenceDurationMS: cfg.STTMaxTurnSilence,\n\t\t\t}))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, elevenlabs.WithElevenLabsSTTKeyterms(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = elevenlabs.NewElevenLabsSTT(cfg.ElevenLabsAPIKey, sttOpts...)\n\tcase providerCartesia:\n\t\tsttOpts := []cartesia.CartesiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTAudioChunkDurationMS != nil {\n\t\t\tsttOpts = append(sttOpts, cartesia.WithCartesiaSTTAudioChunkDurationMS(*cfg.STTAudioChunkDurationMS))\n\t\t}\n\t\ta.STT = cartesia.NewCartesiaSTT(\"\", sttOpts...)\n\tcase providerClova:\n\t\tsttOpts := []clova.ClovaSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, clova.WithClovaSTTThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\ta.STT = ensureSTTStreaming(clova.NewClovaSTT(cfg.ClovaSTTSecret, cfg.ClovaSTTInvokeURL, sttOpts...), a.VAD)\n\tcase providerDeepgram:\n\t\ta.STT = deepgramSTTFromConfig(cfg)\n\tcase providerFal:\n\t\ta.STT = falSTTFromConfig(cfg)\n\tcase providerFireworks:\n\t\tsttOpts := []fireworksai.FireworksSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTSkipVAD != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksSkipVAD(*cfg.STTSkipVAD))\n\t\t}\n\t\tif len(cfg.STTVADKwargs) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksVADKwargs(cfg.STTVADKwargs))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTextTimeoutSeconds(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif len(cfg.STTTimestampGranularities) > 0 {\n\t\t\tsttOpts = append(sttOpts, fireworksai.WithFireworksTimestampGranularities(cfg.STTTimestampGranularities))\n\t\t}\n\t\ta.STT = fireworksai.NewFireworksSTT(cfg.FireworksAPIKey, sttOpts...)\n\tcase providerGladia:\n\t\tsttOpts := []gladia.GladiaSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaLanguages(splitStringList(cfg.STTLanguageOptions)))\n\t\t}\n\t\tif cfg.STTCodeSwitching != nil {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCodeSwitching(*cfg.STTCodeSwitching))\n\t\t}\n\t\tsampleRate := 0\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsampleRate = *cfg.STTSampleRate\n\t\t}\n\t\tbitDepth := 0\n\t\tif cfg.STTBitDepth != nil {\n\t\t\tbitDepth = *cfg.STTBitDepth\n\t\t}\n\t\tchannels := 0\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tchannels = *cfg.STTNumberOfChannels\n\t\t}\n\t\tif sampleRate != 0 || bitDepth != 0 || channels != 0 || cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaAudioFormat(sampleRate, bitDepth, channels, cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil || cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tendpointing := -1.0\n\t\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\t\tendpointing = *cfg.STTEndpointingSeconds\n\t\t\t}\n\t\t\tmaxDuration := 0.0\n\t\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\t\tmaxDuration = *cfg.STTMaxDurationWithoutEndpointingSeconds\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaEndpointing(endpointing, maxDuration))\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaRegion(cfg.STTRegion))\n\t\t}\n\t\tif len(cfg.STTCustomVocabulary) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomVocabulary(cfg.STTCustomVocabulary))\n\t\t}\n\t\tif len(cfg.STTCustomSpelling) > 0 {\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaCustomSpelling(cfg.STTCustomSpelling))\n\t\t}\n\t\tif len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tmatchOriginal := boolValue(cfg.STTTranslationMatchOriginalUtterances)\n\t\t\tlipsync := boolValue(cfg.STTTranslationLipsync)\n\t\t\tcontextAdaptation := boolValue(cfg.STTTranslationContextAdaptation)\n\t\t\tinformal := boolValue(cfg.STTTranslationInformal)\n\t\t\tif cfg.STTTranslationModel != \"\" || cfg.STTTranslationContext != \"\" || matchOriginal || lipsync || contextAdaptation || informal {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslationConfig(\n\t\t\t\t\tcfg.STTTranslationTargetLanguages,\n\t\t\t\t\tcfg.STTTranslationModel,\n\t\t\t\t\tmatchOriginal,\n\t\t\t\t\tlipsync,\n\t\t\t\t\tcontextAdaptation,\n\t\t\t\t\tcfg.STTTranslationContext,\n\t\t\t\t\tinformal,\n\t\t\t\t))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaTranslation(cfg.STTTranslationTargetLanguages))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTPreProcessingAudioEnhancer != nil || cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\tspeechThreshold := 0.0\n\t\t\tif cfg.STTPreProcessingSpeechThreshold != nil {\n\t\t\t\tspeechThreshold = *cfg.STTPreProcessingSpeechThreshold\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, gladia.WithGladiaPreProcessing(boolValue(cfg.STTPreProcessingAudioEnhancer), speechThreshold))\n\t\t}\n\t\ta.STT = gladia.NewGladiaSTT(cfg.GladiaAPIKey, sttOpts...)\n\tcase providerGnani:\n\t\tsttOpts := []gnani.STTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTOrganizationID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTOrganizationID(cfg.STTOrganizationID))\n\t\t}\n\t\tif cfg.STTUserID != \"\" {\n\t\t\tsttOpts = append(sttOpts, gnani.WithSTTUserID(cfg.STTUserID))\n\t\t}\n\t\ta.STT = gnani.NewSTT(cfg.GnaniAPIKey, sttOpts...)\n\tcase providerGradium:\n\t\tsttOpts := []gradium.GradiumSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelEndpoint(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTModelName(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTemperature != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTTemperature(*cfg.STTTemperature))\n\t\t}\n\t\tif cfg.STTVADBucket != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADBucket(cfg.STTVADBucket))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTVADFlush(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, gradium.WithGradiumSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\ta.STT = gradium.NewGradiumSTT(cfg.GradiumAPIKey, sttOpts...)\n\tcase providerInworld:\n\t\tsttOpts := []inworld.InworldSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTVoiceProfile != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfile(*cfg.STTVoiceProfile))\n\t\t}\n\t\tif cfg.STTVoiceProfileTopN != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVoiceProfileTopN(*cfg.STTVoiceProfileTopN))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTMinEndOfTurnSilenceWhenConfident != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTMinEndOfTurnSilenceWhenConfident(*cfg.STTMinEndOfTurnSilenceWhenConfident))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, inworld.WithInworldSTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\ta.STT = inworld.NewInworldSTT(cfg.InworldAPIKey, sttOpts...)\n\tcase providerMistralAI:\n\t\tsttOpts := []mistralai.MistralAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, mistralai.WithMistralAISTTContextBias(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = mistralai.NewMistralAISTT(cfg.MistralAPIKey, sttOpts...)\n\tcase providerNvidia:\n\t\tsttOpts := nvidiaSTTOptionsFromConfig(cfg)\n\t\tprovider, err := nvidia.NewNvidiaSTT(cfg.NvidiaAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerSLNG:\n\t\tsttOpts := []slng.STTOption{}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModel(cfg.STTModel))\n\t\t}\n\t\tif len(cfg.STTModelEndpoints) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelEndpoints(cfg.STTModelEndpoints...))\n\t\t} else if cfg.STTBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.STTBaseURL, \"ws://\") || strings.HasPrefix(cfg.STTBaseURL, \"wss://\") || strings.HasPrefix(cfg.STTBaseURL, \"http://\") || strings.HasPrefix(cfg.STTBaseURL, \"https://\") {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTEndpoint(cfg.STTBaseURL))\n\t\t\t} else {\n\t\t\t\tsttOpts = append(sttOpts, slng.WithSTTBaseURL(cfg.STTBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.STTRegion != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTRegionOverride(cfg.STTRegion))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTPartialTranscripts(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTBufferSizeSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTBufferSizeSeconds(*cfg.STTBufferSizeSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADMinSilenceDurationMS(int(math.Round(*cfg.STTVADSilenceThresholdSeconds*1000))))\n\t\t}\n\t\tif cfg.STTVADSpeechPadMS != nil {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTVADSpeechPadMS(*cfg.STTVADSpeechPadMS))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tminSpeakers := 0\n\t\t\tif cfg.STTMinSpeakers != nil {\n\t\t\t\tminSpeakers = *cfg.STTMinSpeakers\n\t\t\t}\n\t\t\tmaxSpeakers := 0\n\t\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\t\tmaxSpeakers = *cfg.STTMaxSpeakers\n\t\t\t}\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTDiarization(*cfg.STTDiarization, minSpeakers, maxSpeakers))\n\t\t}\n\t\tif len(cfg.STTModelOptions) > 0 {\n\t\t\tsttOpts = append(sttOpts, slng.WithSTTModelOptions(cfg.STTModelOptions))\n\t\t}\n\t\ta.STT = slng.NewSTT(cfg.SLNGAPIKey, sttOpts...)\n\tcase providerSarvam:\n\t\tsttOpts := []sarvam.SarvamSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStreamingURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMode(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTVADEvents != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTHighVADSensitivity(*cfg.STTVADEvents))\n\t\t}\n\t\tif cfg.STTVADFlush != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFlushSignal(*cfg.STTVADFlush))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInputAudioCodec(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTPositiveSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPositiveSpeechThreshold(*cfg.STTPositiveSpeechThreshold))\n\t\t}\n\t\tif cfg.STTNegativeSpeechThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeSpeechThreshold(*cfg.STTNegativeSpeechThreshold))\n\t\t}\n\t\tif cfg.STTMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTMinSpeechFrames(*cfg.STTMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTFirstTurnMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTFirstTurnMinSpeechFrames(*cfg.STTFirstTurnMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTNegativeFramesCount != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesCount(*cfg.STTNegativeFramesCount))\n\t\t}\n\t\tif cfg.STTNegativeFramesWindow != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNegativeFramesWindow(*cfg.STTNegativeFramesWindow))\n\t\t}\n\t\tif cfg.STTStartSpeechVolumeThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTStartSpeechVolumeThreshold(*cfg.STTStartSpeechVolumeThreshold))\n\t\t}\n\t\tif cfg.STTInterruptMinSpeechFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTInterruptMinSpeechFrames(*cfg.STTInterruptMinSpeechFrames))\n\t\t}\n\t\tif cfg.STTPreSpeechPadFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTPreSpeechPadFrames(*cfg.STTPreSpeechPadFrames))\n\t\t}\n\t\tif cfg.STTNumInitialIgnoredFrames != nil {\n\t\t\tsttOpts = append(sttOpts, sarvam.WithSarvamSTTNumInitialIgnoredFrames(*cfg.STTNumInitialIgnoredFrames))\n\t\t}\n\t\tprovider := sarvam.NewSarvamSTT(cfg.SarvamAPIKey, sttOpts...)\n\t\tif provider == nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid sarvam STT configuration\")\n\t\t}\n\t\ta.STT = provider\n\tcase providerRtzr:\n\t\tsttOpts := []rtzr.RtzrSTTOption{}\n\t\tif cfg.RtzrClientSecret != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrClientSecret(cfg.RtzrClientSecret))\n\t\t}\n\t\tif cfg.RtzrAccessToken != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAccessToken(cfg.RtzrAccessToken))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrAPIBase(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrWSBase(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrEPDTime(*cfg.STTEndpointingSeconds))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrNoiseThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrActiveThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTPunctuate != nil {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrUsePunctuation(*cfg.STTPunctuate))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, rtzr.WithRtzrKeywords(cfg.STTKeytermsPrompt))\n\t\t}\n\t\ta.STT = rtzr.NewRtzrSTT(cfg.RtzrClientID, sttOpts...)\n\tcase providerSimplismart:\n\t\tsttOpts := []simplismart.SimplismartSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTStreaming(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTTask != \"\" {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTTask(cfg.STTTask))\n\t\t}\n\t\tif cfg.STTIncludeTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTWithoutTimestamps(!*cfg.STTIncludeTimestamps))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTHotwords(strings.Join(cfg.STTKeytermsPrompt, \",\")))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, simplismart.WithSimplismartSTTNumSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\ta.STT = simplismart.NewSimplismartSTT(cfg.SimplismartAPIKey, sttOpts...)\n\tcase providerSmallestAI:\n\t\tsttOpts := []smallestai.SmallestAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTWordTimestamps != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTWordTimestamps(*cfg.STTWordTimestamps))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTDiarize(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, smallestai.WithSmallestAISTTEOUTimeoutMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = smallestai.NewSmallestAISTT(cfg.SmallestAIAPIKey, sttOpts...)\n\tcase providerSoniox:\n\t\tsttOpts := []soniox.SonioxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTLanguageOptions != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints(splitStringList(cfg.STTLanguageOptions)))\n\t\t} else if cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHints([]string{cfg.STTLanguage}))\n\t\t}\n\t\tif strict := modelOptionBool(cfg.STTModelOptions, \"language_hints_strict\"); strict != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageHintsStrict(*strict))\n\t\t}\n\t\tif cfg.STTNumberOfChannels != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxNumChannels(*cfg.STTNumberOfChannels))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxSpeakerDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxLanguageIdentification(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxMaxEndpointDelayMS(*cfg.STTEndpointingMS))\n\t\t}\n\t\tif cfg.STTSessionID != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxClientReferenceID(cfg.STTSessionID))\n\t\t}\n\t\tif context, ok := sonioxContextObjectFromModelOptions(cfg.STTModelOptions); ok {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextObject(context))\n\t\t} else if cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxContextText(cfg.STTPrompt))\n\t\t}\n\t\tif len(cfg.STTTranslationSourceLanguages) > 0 && len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxTwoWayTranslation(cfg.STTTranslationSourceLanguages[0], cfg.STTTranslationTargetLanguages[0]))\n\t\t} else if len(cfg.STTTranslationTargetLanguages) > 0 {\n\t\t\tsttOpts = append(sttOpts, soniox.WithSonioxOneWayTranslation(cfg.STTTranslationTargetLanguages[0]))\n\t\t}\n\t\ta.STT = soniox.NewSonioxSTT(cfg.SonioxAPIKey, sttOpts...)\n\tcase providerSpeechmatics:\n\t\tsttOpts := []speechmatics.SpeechmaticsSTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTEncoding != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAudioEncoding(cfg.STTEncoding))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTDomain(cfg.STTDomain))\n\t\t}\n\t\tif cfg.STTOutputLocale != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOutputLocale(cfg.STTOutputLocale))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTIncludePartials(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEnableDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTAdditionalVocab(speechmaticsAdditionalVocab(cfg.STTKeytermsPrompt)))\n\t\t}\n\t\tfocusSpeakers := modelOptionStringList(cfg.STTModelOptions, \"focus_speakers\")\n\t\tignoreSpeakers := modelOptionStringList(cfg.STTModelOptions, \"ignore_speakers\")\n\t\tfocusMode := modelOptionString(cfg.STTModelOptions, \"focus_mode\")\n\t\tif len(focusSpeakers) > 0 || len(ignoreSpeakers) > 0 || focusMode != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerFocus(focusSpeakers, ignoreSpeakers, focusMode))\n\t\t}\n\t\tif speakers := speechmaticsKnownSpeakers(modelOptionString(cfg.STTModelOptions, \"known_speakers\")); len(speakers) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTKnownSpeakers(speakers))\n\t\t}\n\t\tif cfg.STTOperatingPoint != \"\" {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTOperatingPoint(cfg.STTOperatingPoint))\n\t\t}\n\t\tif cfg.STTTextTimeoutSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxDelay(*cfg.STTTextTimeoutSeconds))\n\t\t}\n\t\tif cfg.STTVADSilenceThresholdSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceSilenceTrigger(*cfg.STTVADSilenceThresholdSeconds))\n\t\t}\n\t\tif cfg.STTMaxDurationWithoutEndpointingSeconds != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTEndOfUtteranceMaxDelay(*cfg.STTMaxDurationWithoutEndpointingSeconds))\n\t\t}\n\t\tif overrides := speechmaticsPunctuationOverrides(cfg.STTModelOptions); len(overrides) > 0 {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPunctuationOverrides(overrides))\n\t\t}\n\t\tif sensitivity := modelOptionFloat(cfg.STTModelOptions, \"speaker_sensitivity\"); sensitivity != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTSpeakerSensitivity(*sensitivity))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTPreferCurrentSpeaker != nil {\n\t\t\tsttOpts = append(sttOpts, speechmatics.WithSpeechmaticsSTTPreferCurrentSpeaker(*cfg.STTPreferCurrentSpeaker))\n\t\t}\n\t\ta.STT = speechmatics.NewSpeechmaticsSTT(cfg.SpeechmaticsAPIKey, sttOpts...)\n\tcase providerSpitch:\n\t\ta.STT = spitch.NewSpitchSTT(cfg.SpitchAPIKey)\n\tcase providerTelnyx:\n\t\tsttOpts := []telnyx.TelnyxSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTTranscriptionEngine(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, telnyx.WithTelnyxSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = telnyx.NewTelnyxSTT(cfg.TelnyxAPIKey, sttOpts...)\n\tcase providerXAI:\n\t\tsttOpts := []xai.XaiSTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTRestURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTStreamingURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTWebsocketURL(cfg.STTStreamingURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTInterimResults != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTInterimResults(*cfg.STTInterimResults))\n\t\t}\n\t\tif cfg.STTDiarization != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTDiarization(*cfg.STTDiarization))\n\t\t}\n\t\tif cfg.STTEndpointingMS != nil {\n\t\t\tsttOpts = append(sttOpts, xai.WithXaiSTTEndpointing(*cfg.STTEndpointingMS))\n\t\t}\n\t\ta.STT = xai.NewXaiSTT(cfg.XAIAPIKey, sttOpts...)\n\tcase providerAssemblyAI:\n\t\tsttOpts := []assemblyai.AssemblyAISTTOption{}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\tif cfg.STTModel != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTModel(cfg.STTModel))\n\t\t}\n\t\tif cfg.STTMinTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMinTurnSilence(*cfg.STTMinTurnSilence))\n\t\t}\n\t\tif cfg.STTMaxTurnSilence != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxTurnSilence(*cfg.STTMaxTurnSilence))\n\t\t}\n\t\tif cfg.STTEndOfTurnConfidenceThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTEndOfTurnConfidenceThreshold(*cfg.STTEndOfTurnConfidenceThreshold))\n\t\t}\n\t\tif cfg.STTFormatTurns != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTFormatTurns(*cfg.STTFormatTurns))\n\t\t}\n\t\tif cfg.STTLanguageDetection != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTLanguageDetection(*cfg.STTLanguageDetection))\n\t\t}\n\t\tif cfg.STTContinuousPartials != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTContinuousPartials(*cfg.STTContinuousPartials))\n\t\t}\n\t\tif cfg.STTInterruptionDelay != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTInterruptionDelay(*cfg.STTInterruptionDelay))\n\t\t}\n\t\tif len(cfg.STTKeytermsPrompt) > 0 {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTKeytermsPrompt(cfg.STTKeytermsPrompt))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTVADThreshold != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTVADThreshold(*cfg.STTVADThreshold))\n\t\t}\n\t\tif cfg.STTSpeakerLabels != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTSpeakerLabels(*cfg.STTSpeakerLabels))\n\t\t}\n\t\tif cfg.STTMaxSpeakers != nil {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTMaxSpeakers(*cfg.STTMaxSpeakers))\n\t\t}\n\t\tif cfg.STTDomain != \"\" {\n\t\t\tsttOpts = append(sttOpts, assemblyai.WithAssemblyAISTTDomain(cfg.STTDomain))\n\t\t}\n\t\ta.STT = assemblyai.NewAssemblyAISTT(os.Getenv(\"ASSEMBLYAI_API_KEY\"), sttOpts...)\n\tcase providerCavos:\n\t\ta.STT = ensureSTTStreaming(cavosSTTFromConfig(cfg), a.VAD)\n\tcase providerGroq:\n\t\tprovider, err := groqSTTFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOpenAI:\n\t\tsttOpts := []openai.OpenAISTTOption{openai.WithOpenAISTTRealtime(true)}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAISTT(cfg.OpenAIAPIKey, cfg.STTModel, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerOVHCloud:\n\t\tsttOpts := []openai.OpenAISTTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTDetectLanguage {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTDetectLanguage(true))\n\t\t}\n\t\tif cfg.STTPrompt != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTPrompt(cfg.STTPrompt))\n\t\t}\n\t\tif cfg.STTBaseURL != \"\" {\n\t\t\tsttOpts = append(sttOpts, openai.WithOpenAISTTBaseURL(cfg.STTBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOVHCloudOpenAISTT(cfg.STTModel, cfg.OVHCloudAPIKey, sttOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.STT = provider\n\tcase providerLiveKit:\n\t\tsttOpts := []adapterlivekit.STTOption{}\n\t\tif cfg.STTLanguage != \"\" {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTLanguage(cfg.STTLanguage))\n\t\t}\n\t\tif cfg.STTSampleRate != nil {\n\t\t\tsttOpts = append(sttOpts, adapterlivekit.WithSTTSampleRate(*cfg.STTSampleRate))\n\t\t}\n\t\ta.STT = adapterlivekit.NewSTT(cfg.STTModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, sttOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_STT_PROVIDER %q\", cfg.STTProvider)\n\t}\n\tif err := configureSTTFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.TTSProvider) {\n\tcase \"\":\n\tcase providerAWS:\n\t\tttsOpts := []adapteraws.AWSTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSVoice(awspollytypes.VoiceId(cfg.TTSVoice)))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSEngine(awspollytypes.Engine(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSTextType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSTextType(awspollytypes.TextType(cfg.TTSTextType)))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSLanguage(awspollytypes.LanguageCode(cfg.TTSLanguage)))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, adapteraws.WithAWSTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tprovider, err := adapteraws.NewAWSTTS(context.Background(), cfg.AWSRegion, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAzure:\n\t\tttsOpts := azureTTSOptionsFromConfig(cfg)\n\t\tprovider, err := azure.NewAzureTTSWithOptions(\"\", \"\", cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerBaseten:\n\t\tttsOpts := []baseten.BasetenTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, baseten.WithBasetenTTSBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tprovider, err := baseten.NewBasetenTTS(\"\", cfg.TTSModel, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGoogle:\n\t\tprovider, err := appNewGoogleTTS(cfg.GoogleCredentialsFile, googleTTSConfigFromAppConfig(cfg))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerElevenLabs:\n\t\tprovider, err := elevenlabs.NewElevenLabsTTS(cfg.ElevenLabsAPIKey, cfg.TTSVoice, cfg.TTSModel, elevenLabsTTSOptionsFromConfig(cfg)...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerGroq:\n\t\tttsOpts := []groq.GroqTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, groq.WithGroqTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tprovider := groq.NewGroqTTS(cfg.GroqAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\ta.TTS = ensureTTSStreaming(provider)\n\tcase providerCartesia:\n\t\tttsOpts := []cartesia.CartesiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\tif cfg.TTSAPIVersion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaAPIVersion(cfg.TTSAPIVersion))\n\t\t}\n\t\tif cfg.TTSWordTimestamps != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaWordTimestamps(*cfg.TTSWordTimestamps))\n\t\t}\n\t\tif len(cfg.TTSVoiceEmbedding) > 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVoiceEmbedding(cfg.TTSVoiceEmbedding))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cartesia.WithCartesiaPronunciationDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\ta.TTS = cartesia.NewCartesiaTTS(\"\", cfg.TTSVoice, cfg.TTSModel, ttsOpts...)\n\tcase providerCavos:\n\t\ta.TTS = ensureTTSStreaming(cavosTTSFromConfig(cfg))\n\tcase providerClova:\n\t\ta.TTS = clova.NewClovaTTS(cfg.ClovaClientID, cfg.ClovaClientSecret, cfg.TTSVoice)\n\tcase providerDeepgram:\n\t\tttsOpts := []deepgram.DeepgramTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSMIPOptOut != nil {\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSMipOptOut(*cfg.TTSMIPOptOut))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" || cfg.TTSSampleRate != nil {\n\t\t\tsampleRate := 0\n\t\t\tif cfg.TTSSampleRate != nil {\n\t\t\t\tsampleRate = *cfg.TTSSampleRate\n\t\t\t}\n\t\t\tttsOpts = append(ttsOpts, deepgram.WithDeepgramTTSAudioFormat(cfg.TTSEncoding, sampleRate))\n\t\t}\n\t\ta.TTS = deepgram.NewDeepgramTTS(\"\", cfg.TTSModel, ttsOpts...)\n\tcase providerFishAudio:\n\t\tttsOpts := []fishaudio.FishAudioTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSOutputFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSLatencyMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSLatencyMode(cfg.TTSLatencyMode))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, fishaudio.WithFishAudioTTSChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\ta.TTS = fishaudio.NewFishAudioTTS(cfg.FishAudioAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerGnani:\n\t\tttsOpts := []gnani.Option{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithContainer(cfg.TTSResponseFormat))\n\t\t}\n\t\tif cfg.TTSNumberOfChannels != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithNumChannels(*cfg.TTSNumberOfChannels))\n\t\t}\n\t\tif cfg.TTSSampleWidth != nil {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithSampleWidth(*cfg.TTSSampleWidth))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gnani.WithLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = gnani.NewTTS(cfg.GnaniAPIKey, ttsOpts...)\n\tcase providerGradium:\n\t\tttsOpts := []gradium.GradiumTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelEndpoint(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSModelName(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSVoiceID(cfg.TTSVoiceID))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSPronunciationID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, gradium.WithGradiumTTSJSONConfig(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = gradium.NewGradiumTTS(cfg.GradiumAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerHume:\n\t\tttsOpts := []hume.HumeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSModelVersion(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoiceID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceID(cfg.TTSVoiceID, cfg.TTSVoiceProvider))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSVoiceName(cfg.TTSVoice, cfg.TTSVoiceProvider))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSDescription(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSTrailingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSTrailingSilence(*cfg.TTSTrailingSilence))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSInstantMode(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSContextGenerationID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextGenerationID(cfg.TTSContextGenerationID))\n\t\t} else if len(cfg.TTSContextUtterances) > 0 {\n\t\t\tttsOpts = append(ttsOpts, hume.WithHumeTTSContextUtterances(cfg.TTSContextUtterances))\n\t\t}\n\t\ta.TTS = hume.NewHumeTTS(cfg.HumeAPIKey, \"\", ttsOpts...)\n\tcase providerInworld:\n\t\tttsOpts := []inworld.InworldTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBitRate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeakingRate != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSSpeakingRate(*cfg.TTSSpeakingRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSTimestampType != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampType(cfg.TTSTimestampType))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSDeliveryMode(cfg.TTSDeliveryMode))\n\t\t}\n\t\tif cfg.TTSTimestampTransportStrategy != \"\" {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSTimestampTransportStrategy(cfg.TTSTimestampTransportStrategy))\n\t\t}\n\t\tif cfg.TTSBufferCharThreshold != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSBufferCharThreshold(*cfg.TTSBufferCharThreshold))\n\t\t}\n\t\tif cfg.TTSMaxBufferDelayMS != nil {\n\t\t\tttsOpts = append(ttsOpts, inworld.WithInworldTTSMaxBufferDelayMS(*cfg.TTSMaxBufferDelayMS))\n\t\t}\n\t\ta.TTS = inworld.NewInworldTTS(cfg.InworldAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMinimax:\n\t\tttsOpts := []minimax.MinimaxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSAudioFormat(\"pcm\"))\n\t\t}\n\t\tif cfg.TTSEmotion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSEmotion(cfg.TTSEmotion))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSVolume(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, minimax.WithMinimaxTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = minimax.NewMinimaxTTS(cfg.MinimaxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerMistralAI:\n\t\tttsOpts := []mistralai.MistralAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSRefAudio != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSRefAudio(cfg.TTSRefAudio))\n\t\t} else if cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, mistralai.WithMistralAITTSResponseFormat(\"pcm\"))\n\t\t}\n\t\tprovider, err := mistralai.NewMistralAITTS(cfg.MistralAPIKey, \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerMurf:\n\t\tttsOpts := []murf.MurfTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSLocale(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSStyle(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSpeed(int(cfg.TTSSpeed)))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSPitch(*cfg.TTSPitch))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, murf.WithMurfTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = murf.NewMurfTTS(cfg.MurfAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerNvidia:\n\t\tttsOpts := []nvidia.NvidiaTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSServer(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSFunctionID(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSLanguageCode(cfg.TTSLanguage))\n\t\t}\n\t\tif useSSL := modelOptionBool(cfg.TTSModelOptions, \"use_ssl\"); useSSL != nil {\n\t\t\tttsOpts = append(ttsOpts, nvidia.WithNvidiaTTSUseSSL(*useSSL))\n\t\t}\n\t\tprovider, err := nvidia.NewNvidiaTTS(cfg.NvidiaAPIKey, cfg.TTSVoice, ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLMNT:\n\t\tttsOpts := []lmnt.LMNTTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSFormat(\"raw\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, lmnt.WithLMNTTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\ta.TTS = lmnt.NewLMNTTTS(cfg.LMNTAPIKey, \"\", ttsOpts...)\n\tcase providerNeuphonic:\n\t\tttsOpts := []neuphonic.NeuphonicTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSLangCode(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, neuphonic.WithNeuphonicTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\ta.TTS = neuphonic.NewNeuphonicTTS(cfg.NeuphonicAPIKey, \"\", ttsOpts...)\n\tcase providerResemble:\n\t\tttsOpts := []resemble.ResembleTTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, resemble.WithResembleTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = resemble.NewResembleTTS(cfg.ResembleAPIKey, \"\", ttsOpts...)\n\tcase providerRespeecher:\n\t\tttsOpts := []respeecher.RespeecherTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif len(cfg.TTSJSONConfig) > 0 {\n\t\t\tttsOpts = append(ttsOpts, respeecher.WithRespeecherTTSSamplingParams(cfg.TTSJSONConfig))\n\t\t}\n\t\ta.TTS = respeecher.NewRespeecherTTS(cfg.RespeecherAPIKey, \"\", ttsOpts...)\n\tcase providerRime:\n\t\tttsOpts := []rime.RimeTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSBaseURL(cfg.TTSWebsocketURL), rime.WithRimeTTSWebsocket(true))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSLang(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSTimeScaleFactor(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSDeliveryMode != \"\" {\n\t\t\tttsOpts = append(ttsOpts, rime.WithRimeTTSSegment(cfg.TTSDeliveryMode))\n\t\t}\n\t\ta.TTS = rime.NewRimeTTS(cfg.RimeAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSarvam:\n\t\tttsOpts := []sarvam.SarvamTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSWSURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSPitch != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPitch(float64(*cfg.TTSPitch)))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSPace(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSVolume != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSLoudness(*cfg.TTSVolume))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioBitrate(strconv.Itoa(*cfg.TTSBitRate)))\n\t\t}\n\t\tif cfg.TTSBufferSize != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMinBufferSize(*cfg.TTSBufferSize))\n\t\t}\n\t\tif cfg.TTSChunkLength != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSMaxChunkLength(*cfg.TTSChunkLength))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnablePreprocessing(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tif cfg.TTSInstantMode != nil {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSEnableCachedResponses(*cfg.TTSInstantMode))\n\t\t}\n\t\tif cfg.TTSPronunciationDictID != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSDictID(cfg.TTSPronunciationDictID))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, sarvam.WithSarvamTTSOutputAudioCodec(cfg.TTSEncoding))\n\t\t}\n\t\ta.TTS = sarvam.NewSarvamTTS(cfg.SarvamAPIKey, \"\", ttsOpts...)\n\tcase providerSimplismart:\n\t\tttsOpts := []simplismart.SimplismartTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSTemperature != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTemperature(*cfg.TTSTemperature))\n\t\t}\n\t\tif cfg.TTSTopP != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSTopP(*cfg.TTSTopP))\n\t\t}\n\t\tif cfg.TTSMaxTokens != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSMaxTokens(*cfg.TTSMaxTokens))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif leadingSilence := modelOptionBool(cfg.TTSModelOptions, \"leading_silence\"); leadingSilence != nil {\n\t\t\tttsOpts = append(ttsOpts, simplismart.WithSimplismartTTSLeadingSilence(*leadingSilence))\n\t\t}\n\t\ta.TTS = simplismart.NewSimplismartTTS(cfg.SimplismartAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSmallestAI:\n\t\tttsOpts := []smallestai.SmallestAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, smallestai.WithSmallestAITTSOutputFormat(cfg.TTSResponseFormat))\n\t\t}\n\t\ta.TTS = smallestai.NewSmallestAITTS(cfg.SmallestAIAPIKey, \"\", ttsOpts...)\n\tcase providerSoniox:\n\t\tttsOpts := []soniox.SonioxTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSAudioFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBitRate != nil {\n\t\t\tttsOpts = append(ttsOpts, soniox.WithSonioxTTSBitrate(*cfg.TTSBitRate))\n\t\t}\n\t\ta.TTS = soniox.NewSonioxTTS(cfg.SonioxAPIKey, ttsOpts...)\n\tcase providerSpeechify:\n\t\tttsOpts := []speechify.SpeechifyTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(cfg.TTSEncoding))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSEncoding(\"wav_48000\"))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLoudnessNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSLoudnessNormalization(*cfg.TTSLoudnessNormalization))\n\t\t}\n\t\tif cfg.TTSTextNormalization != nil {\n\t\t\tttsOpts = append(ttsOpts, speechify.WithSpeechifyTTSTextNormalization(*cfg.TTSTextNormalization))\n\t\t}\n\t\ta.TTS = speechify.NewSpeechifyTTS(cfg.SpeechifyAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSpeechmatics:\n\t\tttsOpts := []speechmatics.SpeechmaticsTTSOption{}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, speechmatics.WithSpeechmaticsTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = speechmatics.NewSpeechmaticsTTS(cfg.SpeechmaticsAPIKey, ttsOpts...)\n\tcase providerSpitch:\n\t\tttsOpts := []spitch.SpitchTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(cfg.TTSResponseFormat))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSOutputFormat(\"wav\"))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, spitch.WithSpitchTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = spitch.NewSpitchTTS(cfg.SpitchAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerTelnyx:\n\t\tttsOpts := []telnyx.TelnyxTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, telnyx.WithTelnyxTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\ta.TTS = telnyx.NewTelnyxTTS(cfg.TelnyxAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerUpliftAI:\n\t\ta.TTS = upliftai.NewUpliftAITTS(cfg.UpliftAIAPIKey, cfg.TTSVoice)\n\tcase providerXAI:\n\t\tttsOpts := []xai.XaiTTSOption{}\n\t\tif cfg.TTSWebsocketURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSWebsocketURL(cfg.TTSWebsocketURL))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, xai.WithXaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\ta.TTS = xai.NewXaiTTS(cfg.XAIAPIKey, cfg.TTSVoice, ttsOpts...)\n\tcase providerSLNG:\n\t\tttsOpts := []slng.TTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tif strings.HasPrefix(cfg.TTSBaseURL, \"ws://\") || strings.HasPrefix(cfg.TTSBaseURL, \"wss://\") || strings.HasPrefix(cfg.TTSBaseURL, \"http://\") || strings.HasPrefix(cfg.TTSBaseURL, \"https://\") {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSEndpoint(cfg.TTSBaseURL))\n\t\t\t} else {\n\t\t\t\tttsOpts = append(ttsOpts, slng.WithTTSBaseURL(cfg.TTSBaseURL))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSRegion != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSRegionOverride(cfg.TTSRegion))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\tif cfg.TTSSpeed != 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif len(cfg.TTSModelOptions) > 0 {\n\t\t\tttsOpts = append(ttsOpts, slng.WithTTSModelOptions(cfg.TTSModelOptions))\n\t\t}\n\t\ta.TTS = slng.NewTTS(cfg.SLNGAPIKey, ttsOpts...)\n\tcase providerCambai:\n\t\tttsOpts := []cambai.CambaiTTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tif voiceID, err := strconv.Atoi(cfg.TTSVoice); err == nil {\n\t\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSVoiceID(voiceID))\n\t\t\t}\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSOutputFormat(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSUserInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSEnhanceNamedEntities != nil {\n\t\t\tttsOpts = append(ttsOpts, cambai.WithCambaiTTSEnhanceNamedEntities(*cfg.TTSEnhanceNamedEntities))\n\t\t}\n\t\tprovider, err := cambai.NewCambaiTTS(\"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerAsyncAI:\n\t\tttsOpts := []asyncai.AsyncAITTSOption{}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSModel(cfg.TTSModel))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSVoice(cfg.TTSVoice))\n\t\t}\n\t\tif cfg.TTSLanguage != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSLanguage(cfg.TTSLanguage))\n\t\t}\n\t\tif cfg.TTSEncoding != \"\" {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSEncoding(cfg.TTSEncoding))\n\t\t}\n\t\tif cfg.TTSSampleRate != nil {\n\t\t\tttsOpts = append(ttsOpts, asyncai.WithAsyncAITTSSampleRate(*cfg.TTSSampleRate))\n\t\t}\n\t\ta.TTS = asyncai.NewAsyncAITTS(os.Getenv(\"ASYNCAI_API_KEY\"), cfg.TTSVoice, ttsOpts...)\n\tcase providerOpenAI:\n\t\tttsOpts := []openai.OpenAITTSOption{}\n\t\tif cfg.TTSModel != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSModel(goopenai.SpeechModel(cfg.TTSModel)))\n\t\t}\n\t\tif cfg.TTSVoice != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSVoice(goopenai.SpeechVoice(cfg.TTSVoice)))\n\t\t}\n\t\tif appTTSSpeedConfigured(cfg) {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSSpeed(cfg.TTSSpeed))\n\t\t}\n\t\tif cfg.TTSInstructions != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSInstructions(cfg.TTSInstructions))\n\t\t}\n\t\tif cfg.TTSResponseFormat != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormat(cfg.TTSResponseFormat)))\n\t\t} else {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSResponseFormat(goopenai.SpeechResponseFormatPcm))\n\t\t}\n\t\tif cfg.TTSBaseURL != \"\" {\n\t\t\tttsOpts = append(ttsOpts, openai.WithOpenAITTSBaseURL(cfg.TTSBaseURL))\n\t\t}\n\t\tprovider, err := openai.NewOpenAITTS(cfg.OpenAIAPIKey, \"\", \"\", ttsOpts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = provider\n\tcase providerLiveKit:\n\t\tttsOpts, err := liveKitTTSOptionsFromConfig(cfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ta.TTS = adapterlivekit.NewTTS(cfg.TTSModel, cfg.LiveKitInferenceAPIKey, cfg.LiveKitInferenceAPISecret, ttsOpts...)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_PROVIDER %q\", cfg.TTSProvider)\n\t}\n\tif err := configureTTSFallbacks(cfg, a); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch normalizeProvider(cfg.RealtimeProvider) {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase providerOpenAI:\n\t\treturn openai.NewRealtimeModel(cfg.OpenAIAPIKey, cfg.RealtimeModel), nil\n\tcase providerXAI:\n\t\topts := []xai.XaiRealtimeOption{}\n\t\tif cfg.RealtimeModel != \"\" {\n\t\t\topts = append(opts, xai.WithXaiRealtimeModel(cfg.RealtimeModel))\n\t\t}\n\t\treturn xai.NewXaiRealtimeModel(cfg.XAIAPIKey, opts...), nil\n\tcase providerPhonic:\n\t\treturn phonic.NewRealtimeModel(cfg.PhonicAPIKey)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_REALTIME_PROVIDER %q\", cfg.RealtimeProvider)\n\t}\n}\n\nfunc nvidiaSTTOptionsFromConfig(cfg AppConfig) []nvidia.NvidiaSTTOption {\n\tsttOpts := []nvidia.NvidiaSTTOption{}\n\tif cfg.STTBaseURL != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTServer(cfg.STTBaseURL))\n\t}\n\tif cfg.STTLanguage != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTLanguage(cfg.STTLanguage))\n\t}\n\tif cfg.STTSampleRate != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTSampleRate(*cfg.STTSampleRate))\n\t}\n\tif functionID := modelOptionString(cfg.STTModelOptions, \"function_id\"); functionID != \"\" {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTFunctionID(functionID))\n\t}\n\tif useSSL := modelOptionBool(cfg.STTModelOptions, \"use_ssl\"); useSSL != nil {\n\t\tsttOpts = append(sttOpts, nvidia.WithNvidiaSTTUseSSL(*useSSL))\n\t}\n\treturn sttOpts\n}\n\nfunc normalizedEnv(name string) string {\n\treturn normalizeProvider(os.Getenv(name))\n}\n\nfunc normalizeProvider(provider string) string {\n\treturn strings.ToLower(strings.TrimSpace(provider))\n}\n\nfunc agentSessionOptionsFromConfig(cfg AppConfig) (agent.AgentSessionOptions, error) {\n\topts := agent.AgentSessionOptions{}\n\tif cfg.TTSStreamPacerEnabled {\n\t\tpacer := coretts.SentenceStreamPacerOptions{}\n\t\tif cfg.TTSStreamPacerMinRemainingAudioMS != nil {\n\t\t\tpacer.MinRemainingAudio = time.Duration(*cfg.TTSStreamPacerMinRemainingAudioMS) * time.Millisecond\n\t\t\tpacer.MinRemainingAudioSet = true\n\t\t}\n\t\tif cfg.TTSStreamPacerMaxTextLength != nil {\n\t\t\tpacer.MaxTextLength = *cfg.TTSStreamPacerMaxTextLength\n\t\t\tpacer.MaxTextLengthSet = true\n\t\t}\n\t\topts.TTSStreamPacer = &pacer\n\t}\n\topts.TTSTextReplacements = cfg.TTSTextReplacements\n\tif len(cfg.TTSTextTransforms) > 0 {\n\t\topts.TTSTextTransforms = append([]string(nil), cfg.TTSTextTransforms...)\n\t\topts.TTSTextTransformsSet = true\n\t}\n\topts.DisableTTSTextTransforms = cfg.DisableTTSTextTransforms\n\topts.LLMParallelToolCalls = cfg.LLMParallelToolCalls\n\topts.LLMExtraParams = cfg.LLMExtraBody\n\topts.LLMResponseFormat = cfg.LLMResponseFormat\n\tif cfg.BackgroundAudioAmbient != \"\" || cfg.BackgroundAudioThinking != \"\" {\n\t\topts.BackgroundAudio = agent.NewBackgroundAudioPlayer(\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioAmbient),\n\t\t\tbackgroundAudioSource(cfg.BackgroundAudioThinking),\n\t\t)\n\t}\n\topts.IVRDetection = cfg.IVRDetection\n\tif cfg.IVRSilenceDurationSeconds != nil {\n\t\topts.IVRSilenceDuration = time.Duration(*cfg.IVRSilenceDurationSeconds * float64(time.Second))\n\t}\n\twordTokenizer, err := wordTokenizerFromConfig(cfg)\n\tif err != nil {\n\t\treturn opts, err\n\t}\n\topts.WordTokenizer = wordTokenizer\n\treturn opts, nil\n}\n\nfunc wordTokenizerFromConfig(cfg AppConfig) (tokenize.WordTokenizer, error) {\n\tprovider := normalizeProvider(cfg.WordTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\tswitch provider {\n\tcase \"basic\":\n\t\treturn tokenize.NewBasicWordTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewWordTokenizer(cfg.WordTokenizerLanguage), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_WORD_TOKENIZER_PROVIDER %q\", cfg.WordTokenizerProvider)\n\t}\n}\n\nfunc backgroundAudioSource(value string) interface{} {\n\tswitch strings.TrimSpace(value) {\n\tcase \"\":\n\t\treturn nil\n\tcase string(agent.CityAmbience):\n\t\treturn agent.CityAmbience\n\tcase string(agent.ForestAmbience):\n\t\treturn agent.ForestAmbience\n\tcase string(agent.OfficeAmbience):\n\t\treturn agent.OfficeAmbience\n\tcase string(agent.CrowdedRoom):\n\t\treturn agent.CrowdedRoom\n\tcase string(agent.KeyboardTyping):\n\t\treturn agent.KeyboardTyping\n\tcase string(agent.KeyboardTyping2):\n\t\treturn agent.KeyboardTyping2\n\tcase string(agent.HoldMusic):\n\t\treturn agent.HoldMusic\n\tdefault:\n\t\treturn value\n\t}\n}\n\nfunc ttsSentenceTokenizer(cfg AppConfig) (tokenize.SentenceTokenizer, error) {\n\tprovider := normalizeProvider(cfg.TTSTokenizerProvider)\n\tif provider == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tminSentenceLen := 0\n\tif cfg.TTSTokenizerMinSentenceLen != nil {\n\t\tminSentenceLen = *cfg.TTSTokenizerMinSentenceLen\n\t}\n\tstreamContextLen := 0\n\tif cfg.TTSTokenizerStreamContextLen != nil {\n\t\tstreamContextLen = *cfg.TTSTokenizerStreamContextLen\n\t}\n\n\tswitch provider {\n\tcase \"advanced\":\n\t\treturn tokenize.NewAdvancedSentenceTokenizer(), nil\n\tcase \"blingfire\":\n\t\treturn blingfire.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tcase \"nltk\":\n\t\treturn nltk.NewSentenceTokenizer(cfg.TTSTokenizerLanguage, minSentenceLen, streamContextLen), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported RTP_AGENT_TTS_TOKENIZER_PROVIDER %q\", cfg.TTSTokenizerProvider)\n\t}\n}\n\nfunc getenvDefault(name, fallback string) string {\n\tif value := os.Getenv(name); value != \"\" {\n\t\treturn value\n\t}\n\treturn fallback\n}\n\nfunc getenvTrimmedDefaultUnsetOnly(name, fallback string) string {\n\tvalue, ok := os.LookupEnv(name)\n\tif !ok {\n\t\treturn fallback\n\t}\n\treturn strings.TrimSpace(value)\n}\n\nfunc firstEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := os.Getenv(name); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc firstTrimmedEnv(names ...string) string {\n\tfor _, name := range names {\n\t\tif value := strings.TrimSpace(os.Getenv(name)); value != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc envIsSet(name string) bool {\n\t_, ok := os.LookupEnv(name)\n\treturn ok\n}\n\nfunc getenvBool(name string) bool {\n\tvalue, err := strconv.ParseBool(os.Getenv(name))\n\treturn err == nil && value\n}\n\nfunc getenvOptionalBool(name string) *bool {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvFloat(name string) float64 {\n\tvalue, err := strconv.ParseFloat(os.Getenv(name), 64)\n\tif err != nil {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc getenvOptionalFloat(name string) *float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.ParseFloat(raw, 64)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc getenvOptionalInt(name string) *int {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalue, err := strconv.Atoi(raw)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn &value\n}\n\nfunc splitEnvList(name string) []string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\treturn splitStringList(raw)\n}\n\nfunc mcpStdioServersFromEnv(name string) []MCPStdioServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPStdioServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc mcpHTTPServersFromEnv(name string) []MCPHTTPServerConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar servers []MCPHTTPServerConfig\n\tif err := json.Unmarshal([]byte(raw), &servers); err != nil {\n\t\treturn nil\n\t}\n\treturn servers\n}\n\nfunc sipOutboundConfigFromEnv(name string) *livekit.SIPOutboundConfig {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tconfig := &livekit.SIPOutboundConfig{}\n\tif err := json.Unmarshal([]byte(raw), config); err != nil {\n\t\treturn nil\n\t}\n\treturn config\n}\n\nfunc jsonEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil\n\t}\n\tvar values map[string]any\n\tif err := json.Unmarshal([]byte(raw), &values); err != nil {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitStringList(raw string) []string {\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvAnyList(name string) []any {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\titems := make([]any, 0, len(values))\n\tfor _, value := range values {\n\t\titems = append(items, value)\n\t}\n\treturn items\n}\n\nfunc splitEnvStringSliceMap(name string) map[string][]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string][]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValues, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\titems := make([]string, 0)\n\t\tfor _, value := range strings.Split(rawValues, \"|\") {\n\t\t\tvalue = strings.TrimSpace(value)\n\t\t\tif value != \"\" {\n\t\t\t\titems = append(items, value)\n\t\t\t}\n\t\t}\n\t\tif len(items) > 0 {\n\t\t\tvalues[key] = items\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc boolValue(value *bool) bool {\n\treturn value != nil && *value\n}\n\nfunc modelOptionString(options map[string]any, key string) string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\ttext, ok := value.(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSpace(text)\n}\n\nfunc modelOptionStringMap(options map[string]any, key string) map[string]string {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tswitch typed := value.(type) {\n\tcase map[string]string:\n\t\tfor k, v := range typed {\n\t\t\tk = strings.TrimSpace(k)\n\t\t\tv = strings.TrimSpace(v)\n\t\t\tif k != \"\" && v != \"\" {\n\t\t\t\tvalues[k] = v\n\t\t\t}\n\t\t}\n\tcase map[string]any:\n\t\tfor k, v := range typed {\n\t\t\ttext, ok := v.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tk = strings.TrimSpace(k)\n\t\t\ttext = strings.TrimSpace(text)\n\t\t\tif k != \"\" && text != \"\" {\n\t\t\t\tvalues[k] = text\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc modelOptionBool(options map[string]any, key string) *bool {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase bool:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseBool(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tcase float64:\n\t\tparsed := typed != 0\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionFloat(options map[string]any, key string) *float64 {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn nil\n\t}\n\tswitch typed := value.(type) {\n\tcase float64:\n\t\treturn &typed\n\tcase string:\n\t\tparsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn &parsed\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc modelOptionInt(options map[string]any, key string) int {\n\tvalue, ok := modelOptionIntValue(options, key)\n\tif !ok {\n\t\treturn 0\n\t}\n\treturn value\n}\n\nfunc modelOptionIntValue(options map[string]any, key string) (int, bool) {\n\tvalue, ok := options[key]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tswitch typed := value.(type) {\n\tcase int:\n\t\treturn typed, true\n\tcase bool:\n\t\tif typed {\n\t\t\treturn 1, true\n\t\t}\n\t\treturn 0, true\n\tcase float64:\n\t\treturn int(typed), true\n\tcase string:\n\t\tparsed, err := strconv.Atoi(strings.TrimSpace(typed))\n\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\t\treturn parsed, true\n\tdefault:\n\t\treturn 0, false\n\t}\n}\n\nfunc modelOptionStringList(options map[string]any, key string) []string {\n\traw := modelOptionString(options, key)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc sonioxContextObjectFromModelOptions(options map[string]any) (soniox.SonioxContextObject, bool) {\n\tvar context soniox.SonioxContextObject\n\tcontext.Text = modelOptionString(options, \"context_text\")\n\tcontext.Terms = modelOptionStringList(options, \"context_terms\")\n\tcontext.General = sonioxContextGeneralItems(modelOptionString(options, \"context_general\"))\n\tcontext.TranslationTerms = sonioxContextTranslationTerms(modelOptionString(options, \"context_translation_terms\"))\n\tok := context.Text != \"\" || len(context.Terms) > 0 || len(context.General) > 0 || len(context.TranslationTerms) > 0\n\treturn context, ok\n}\n\nfunc sonioxContextGeneralItems(raw string) []soniox.SonioxContextGeneralItem {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\titems := make([]soniox.SonioxContextGeneralItem, 0, len(parts))\n\tfor _, part := range parts {\n\t\tkey, value, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\tvalue = strings.TrimSpace(value)\n\t\tif key != \"\" && value != \"\" {\n\t\t\titems = append(items, soniox.SonioxContextGeneralItem{Key: key, Value: value})\n\t\t}\n\t}\n\treturn items\n}\n\nfunc sonioxContextTranslationTerms(raw string) []soniox.SonioxContextTranslationTerm {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tterms := make([]soniox.SonioxContextTranslationTerm, 0, len(parts))\n\tfor _, part := range parts {\n\t\tsource, target, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tsource = strings.TrimSpace(source)\n\t\ttarget = strings.TrimSpace(target)\n\t\tif source != \"\" && target != \"\" {\n\t\t\tterms = append(terms, soniox.SonioxContextTranslationTerm{Source: source, Target: target})\n\t\t}\n\t}\n\treturn terms\n}\n\nfunc speechmaticsAdditionalVocab(values []string) []speechmatics.SpeechmaticsAdditionalVocabEntry {\n\tentries := make([]speechmatics.SpeechmaticsAdditionalVocabEntry, 0, len(values))\n\tfor _, value := range values {\n\t\tcontent, soundsLike, _ := strings.Cut(value, \":\")\n\t\tcontent = strings.TrimSpace(content)\n\t\tif content == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tentry := speechmatics.SpeechmaticsAdditionalVocabEntry{Content: content}\n\t\tif soundsLike != \"\" {\n\t\t\tentry.SoundsLike = splitPipeList(soundsLike)\n\t\t}\n\t\tentries = append(entries, entry)\n\t}\n\treturn entries\n}\n\nfunc speechmaticsKnownSpeakers(raw string) []speechmatics.SpeechmaticsSpeakerIdentifier {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tspeakers := make([]speechmatics.SpeechmaticsSpeakerIdentifier, 0, len(parts))\n\tfor _, part := range parts {\n\t\tlabel, speakerID, ok := strings.Cut(part, \":\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlabel = strings.TrimSpace(label)\n\t\tspeakerID = strings.TrimSpace(speakerID)\n\t\tif label != \"\" && speakerID != \"\" {\n\t\t\tspeakers = append(speakers, speechmatics.SpeechmaticsSpeakerIdentifier{Label: label, SpeakerID: speakerID})\n\t\t}\n\t}\n\treturn speakers\n}\n\nfunc speechmaticsPunctuationOverrides(options map[string]any) map[string]interface{} {\n\tmarks := modelOptionStringList(options, \"permitted_marks\")\n\tif len(marks) == 0 {\n\t\treturn nil\n\t}\n\treturn map[string]interface{}{\"permitted_marks\": marks}\n}\n\nfunc anthropicProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.AnthropicTools))\n\tfor _, tool := range cfg.AnthropicTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"computer\", \"computer_use\", \"computeruse\":\n\t\t\twidth := 1024\n\t\t\tif cfg.AnthropicComputerWidth != nil {\n\t\t\t\twidth = *cfg.AnthropicComputerWidth\n\t\t\t}\n\t\t\theight := 768\n\t\t\tif cfg.AnthropicComputerHeight != nil {\n\t\t\t\theight = *cfg.AnthropicComputerHeight\n\t\t\t}\n\t\t\tcomputer := anthropic.NewComputerTool(browser.NewPageActions(), width, height)\n\t\t\ttools = append(tools, computer.Tools()...)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc configureAppTools(cfg AppConfig, a *agent.Agent, session *agent.AgentSession) error {\n\tif len(cfg.AppTools) == 0 {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"end_call\", \"endcall\":\n\t\t\ttools = append(tools, betatools.NewSessionEndCallTool(session, betatools.EndCallToolOptions{}))\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = append(a.Tools, tools...)\n\treturn nil\n}\n\nfunc configureMCPTools(ctx context.Context, cfg AppConfig, a *agent.Agent) ([]llm.MCPServer, error) {\n\tif len(cfg.MCPStdioServers) == 0 && len(cfg.MCPHTTPServers) == 0 {\n\t\treturn nil, nil\n\t}\n\tif a == nil {\n\t\treturn nil, fmt.Errorf(\"MCP tools require an agent\")\n\t}\n\n\tservers := make([]llm.MCPServer, 0, len(cfg.MCPStdioServers)+len(cfg.MCPHTTPServers))\n\tfor _, serverConfig := range cfg.MCPStdioServers {\n\t\tcommand := strings.TrimSpace(serverConfig.Command)\n\t\tif command == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_STDIO_SERVERS entry requires command\")\n\t\t}\n\t\tserver := llm.NewMCPServerStdio(command, serverConfig.Args)\n\t\tserver.Env = serverConfig.Env\n\t\tserver.Cwd = serverConfig.Cwd\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\tfor _, serverConfig := range cfg.MCPHTTPServers {\n\t\turl := strings.TrimSpace(serverConfig.URL)\n\t\tif url == \"\" {\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, fmt.Errorf(\"RTP_AGENT_MCP_HTTP_SERVERS entry requires url\")\n\t\t}\n\t\tserver := appNewMCPServerHTTP(url)\n\t\tserver.TransportType = serverConfig.TransportType\n\t\tserver.AllowedTools = serverConfig.AllowedTools\n\t\tserver.Headers = serverConfig.Headers\n\n\t\tinitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\terr := server.Initialize(initCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlistCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\ttools, err := server.ListTools(listCtx)\n\t\tcancel()\n\t\tif err != nil {\n\t\t\t_ = server.Close()\n\t\t\tcloseMCPServers(servers)\n\t\t\treturn nil, err\n\t\t}\n\n\t\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\t\tservers = append(servers, server)\n\t}\n\treturn servers, nil\n}\n\nfunc configureRoomTools(cfg AppConfig, a *agent.Agent, publisher betatools.DtmfPublisher) error {\n\tif len(cfg.AppTools) == 0 && !cfg.IVRDetection {\n\t\treturn nil\n\t}\n\ttools := make([]llm.Tool, 0, len(cfg.AppTools))\n\tif cfg.IVRDetection {\n\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t}\n\tfor _, tool := range cfg.AppTools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"send_dtmf\", \"send_dtmf_events\", \"senddtmf\":\n\t\t\ttools = append(tools, betatools.NewSendDTMFTool(publisher))\n\t\tcase \"end_call\", \"endcall\":\n\t\t\tcontinue\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported RTP_AGENT_TOOLS value %q\", tool)\n\t\t}\n\t}\n\ta.Tools = appendMissingToolsByID(a.Tools, tools...)\n\treturn nil\n}\n\nfunc appendMissingToolsByID(existing []llm.Tool, additions ...llm.Tool) []llm.Tool {\n\tseen := make(map[string]struct{}, len(existing)+len(additions))\n\tfor _, tool := range existing {\n\t\tif tool != nil {\n\t\t\tseen[tool.ID()] = struct{}{}\n\t\t}\n\t}\n\tfor _, tool := range additions {\n\t\tif tool == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := seen[tool.ID()]; ok {\n\t\t\tcontinue\n\t\t}\n\t\texisting = append(existing, tool)\n\t\tseen[tool.ID()] = struct{}{}\n\t}\n\treturn existing\n}\n\nfunc xaiProviderTools(cfg AppConfig) []llm.Tool {\n\ttools := make([]llm.Tool, 0, len(cfg.XAITools))\n\tfor _, tool := range cfg.XAITools {\n\t\tswitch normalizeProvider(tool) {\n\t\tcase \"web_search\", \"websearch\", \"xai_web_search\":\n\t\t\ttools = append(tools, &xai.WebSearchTool{})\n\t\tcase \"x_search\", \"xsearch\", \"xai_x_search\":\n\t\t\ttools = append(tools, &xai.XSearchTool{AllowedHandles: cfg.XAIAllowedXHandles})\n\t\tcase \"file_search\", \"filesearch\", \"xai_file_search\":\n\t\t\titem := &xai.FileSearchTool{VectorStoreIDs: cfg.XAIFileSearchVectorStoreIDs}\n\t\t\tif cfg.XAIFileSearchMaxResults != nil {\n\t\t\t\titem.MaxNumResults = *cfg.XAIFileSearchMaxResults\n\t\t\t}\n\t\t\ttools = append(tools, item)\n\t\t}\n\t}\n\treturn tools\n}\n\nfunc splitPipeList(raw string) []string {\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \"|\")\n\tvalues := make([]string, 0, len(parts))\n\tfor _, part := range parts {\n\t\tvalue := strings.TrimSpace(part)\n\t\tif value != \"\" {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvFloatList(name string) []float64 {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tvalues := make([]float64, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue, err := strconv.ParseFloat(rawValue, 64)\n\t\tif err == nil {\n\t\t\tvalues = append(values, value)\n\t\t}\n\t}\n\treturn values\n}\n\nfunc splitEnvHumeTTSUtterances(name string) []hume.HumeTTSUtterance {\n\tvalues := splitEnvList(name)\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\tutterances := make([]hume.HumeTTSUtterance, 0, len(values))\n\tfor _, value := range values {\n\t\tutterances = append(utterances, hume.HumeTTSUtterance{Text: value})\n\t}\n\treturn utterances\n}\n\nfunc splitEnvDeepgramKeywords(name string) []deepgram.DeepgramKeyword {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tparts := strings.Split(raw, \",\")\n\tkeywords := make([]deepgram.DeepgramKeyword, 0, len(parts))\n\tfor _, part := range parts {\n\t\trawValue := strings.TrimSpace(part)\n\t\tif rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeyword := deepgram.DeepgramKeyword{Keyword: rawValue}\n\t\tif name, boost, ok := strings.Cut(rawValue, \":\"); ok {\n\t\t\tif parsedBoost, err := strconv.ParseFloat(strings.TrimSpace(boost), 64); err == nil {\n\t\t\t\tkeyword.Keyword = strings.TrimSpace(name)\n\t\t\t\tkeyword.Boost = parsedBoost\n\t\t\t}\n\t\t}\n\t\tif keyword.Keyword != \"\" {\n\t\t\tkeywords = append(keywords, keyword)\n\t\t}\n\t}\n\treturn keywords\n}\n\nfunc splitEnvMap(name string) map[string]any {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]any{}\n\tfor _, part := range splitEnvMapParts(raw) {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key == \"\" || rawValue == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalues[key] = parseEnvMapValue(rawValue)\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n\nfunc splitEnvMapParts(raw string) []string {\n\tparts := []string{}\n\tstart := 0\n\tdepth := 0\n\tinString := false\n\tescaped := false\n\tfor i, r := range raw {\n\t\tif escaped {\n\t\t\tescaped = false\n\t\t\tcontinue\n\t\t}\n\t\tif inString && r == '\\\\' {\n\t\t\tescaped = true\n\t\t\tcontinue\n\t\t}\n\t\tif r == '\"' {\n\t\t\tinString = !inString\n\t\t\tcontinue\n\t\t}\n\t\tif inString {\n\t\t\tcontinue\n\t\t}\n\t\tswitch r {\n\t\tcase '[', '{':\n\t\t\tdepth++\n\t\tcase ']', '}':\n\t\t\tif depth > 0 {\n\t\t\t\tdepth--\n\t\t\t}\n\t\tcase ',':\n\t\t\tif depth == 0 {\n\t\t\t\tparts = append(parts, raw[start:i])\n\t\t\t\tstart = i + 1\n\t\t\t}\n\t\t}\n\t}\n\tparts = append(parts, raw[start:])\n\treturn parts\n}\n\nfunc parseEnvMapValue(rawValue string) any {\n\tif value, err := strconv.ParseBool(rawValue); err == nil {\n\t\treturn value\n\t}\n\tif value, err := strconv.ParseFloat(rawValue, 64); err == nil {\n\t\treturn value\n\t}\n\tif strings.HasPrefix(rawValue, \"[\") || strings.HasPrefix(rawValue, \"{\") {\n\t\tvar decoded any\n\t\tif err := json.Unmarshal([]byte(rawValue), &decoded); err == nil {\n\t\t\treturn decoded\n\t\t}\n\t}\n\treturn rawValue\n}\n\nfunc splitEnvStringMap(name string) map[string]string {\n\traw := os.Getenv(name)\n\tif raw == \"\" {\n\t\treturn nil\n\t}\n\tvalues := map[string]string{}\n\tfor _, part := range strings.Split(raw, \",\") {\n\t\tkey, rawValue, ok := strings.Cut(part, \"=\")\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tkey = strings.TrimSpace(key)\n\t\trawValue = strings.TrimSpace(rawValue)\n\t\tif key != \"\" && rawValue != \"\" {\n\t\t\tvalues[key] = rawValue\n\t\t}\n\t}\n\tif len(values) == 0 {\n\t\treturn nil\n\t}\n\treturn values\n}\n"}}}}